2 * Copyright (C) 2006 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License, version 2.1, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org
.spearce
.jgit
.lib
;
19 import java
.io
.IOException
;
20 import java
.util
.Arrays
;
22 import org
.spearce
.jgit
.errors
.CorruptObjectException
;
23 import org
.spearce
.jgit
.errors
.EntryExistsException
;
24 import org
.spearce
.jgit
.errors
.MissingObjectException
;
26 public class Tree
extends TreeEntry
implements Treeish
{
27 public static final TreeEntry
[] EMPTY_TREE
= {};
29 public static final int compareNames(final byte[] a
, final byte[] b
) {
30 return compareNames(a
, b
, 0, b
.length
);
33 public static final int compareNames(final byte[] a
, final byte[] nameUTF8
,
34 final int nameStart
, final int nameEnd
) {
35 for (int j
= 0, k
= nameStart
; j
< a
.length
&& k
< nameEnd
; j
++, k
++) {
36 final int aj
= a
[j
] & 0xff;
37 final int bk
= nameUTF8
[k
] & 0xff;
44 final int namelength
= nameEnd
- nameStart
;
45 if (a
.length
== namelength
)
47 else if (a
.length
< namelength
)
53 private static final byte[] substring(final byte[] s
, final int nameStart
,
55 if (nameStart
== 0 && nameStart
== s
.length
)
57 final byte[] n
= new byte[nameEnd
- nameStart
];
58 System
.arraycopy(s
, nameStart
, n
, 0, n
.length
);
62 private static final int binarySearch(final TreeEntry
[] entries
,
63 final byte[] nameUTF8
, final int nameStart
, final int nameEnd
) {
64 if (entries
.length
== 0)
66 int high
= entries
.length
;
69 final int mid
= (low
+ high
) / 2;
70 final int cmp
= compareNames(entries
[mid
].getNameUTF8(), nameUTF8
,
82 private final Repository db
;
84 private TreeEntry
[] contents
;
86 public Tree(final Repository repo
) {
87 super(null, null, null);
89 contents
= EMPTY_TREE
;
92 public Tree(final Repository repo
, final ObjectId myId
, final byte[] raw
)
94 super(null, myId
, null);
99 private Tree(final Tree parent
, final byte[] nameUTF8
) {
100 super(parent
, null, nameUTF8
);
101 db
= parent
.getRepository();
102 contents
= EMPTY_TREE
;
105 public Tree(final Tree parent
, final ObjectId id
, final byte[] nameUTF8
) {
106 super(parent
, id
, nameUTF8
);
107 db
= parent
.getRepository();
110 public FileMode
getMode() {
111 return FileMode
.TREE
;
114 public boolean isRoot() {
115 return getParent() == null;
118 public Repository
getRepository() {
122 public final ObjectId
getTreeId() {
126 public final Tree
getTree() {
130 public boolean isLoaded() {
131 return contents
!= null;
134 public void unload() {
136 throw new IllegalStateException("Cannot unload a modified tree.");
140 public FileTreeEntry
addFile(final String name
) throws IOException
{
141 return addFile(name
.getBytes(Constants
.CHARACTER_ENCODING
), 0);
144 public FileTreeEntry
addFile(final byte[] s
, final int offset
)
149 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++) {
150 // search for path component terminator
154 p
= binarySearch(contents
, s
, offset
, slash
);
155 if (p
>= 0 && slash
< s
.length
&& contents
[p
] instanceof Tree
)
156 return ((Tree
) contents
[p
]).addFile(s
, slash
+ 1);
158 final byte[] newName
= substring(s
, offset
, slash
);
160 throw new EntryExistsException(new String(newName
,
161 Constants
.CHARACTER_ENCODING
));
162 else if (slash
< s
.length
) {
163 final Tree t
= new Tree(this, newName
);
165 return t
.addFile(s
, slash
+ 1);
167 final FileTreeEntry f
= new FileTreeEntry(this, null, newName
,
174 public Tree
addTree(final String name
) throws IOException
{
175 return addTree(name
.getBytes(Constants
.CHARACTER_ENCODING
), 0);
178 public Tree
addTree(final byte[] s
, final int offset
) throws IOException
{
182 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++) {
183 // search for path component terminator
187 p
= binarySearch(contents
, s
, offset
, slash
);
188 if (p
>= 0 && slash
< s
.length
&& contents
[p
] instanceof Tree
)
189 return ((Tree
) contents
[p
]).addTree(s
, slash
+ 1);
191 final byte[] newName
= substring(s
, offset
, slash
);
193 throw new EntryExistsException(new String(newName
,
194 Constants
.CHARACTER_ENCODING
));
196 final Tree t
= new Tree(this, newName
);
198 return slash
== s
.length ? t
: t
.addTree(s
, slash
+ 1);
201 public void addEntry(final TreeEntry e
) throws IOException
{
205 p
= binarySearch(contents
, e
.getNameUTF8(), 0, e
.getNameUTF8().length
);
207 e
.attachParent(this);
210 throw new EntryExistsException(new String(e
.getNameUTF8(),
211 Constants
.CHARACTER_ENCODING
));
215 private void insertEntry(int p
, final TreeEntry e
) {
216 final TreeEntry
[] c
= contents
;
217 final TreeEntry
[] n
= new TreeEntry
[c
.length
+ 1];
219 for (int k
= c
.length
- 1; k
>= p
; k
--)
222 for (int k
= p
- 1; k
>= 0; k
--)
228 void removeEntry(final TreeEntry e
) {
229 final TreeEntry
[] c
= contents
;
230 final int p
= binarySearch(c
, e
.getNameUTF8(), 0,
231 e
.getNameUTF8().length
);
233 final TreeEntry
[] n
= new TreeEntry
[c
.length
- 1];
234 for (int k
= c
.length
- 1; k
> p
; k
--)
236 for (int k
= p
- 1; k
>= 0; k
--)
243 public int memberCount() throws IOException
{
245 return contents
.length
;
248 public TreeEntry
[] members() throws IOException
{
250 final TreeEntry
[] c
= contents
;
252 final TreeEntry
[] r
= new TreeEntry
[c
.length
];
253 for (int k
= c
.length
- 1; k
>= 0; k
--)
260 public boolean exists(final String s
) throws IOException
{
261 return findMember(s
) != null;
264 public TreeEntry
findMember(final String s
) throws IOException
{
265 return findMember(s
.getBytes(Constants
.CHARACTER_ENCODING
), 0);
268 public TreeEntry
findMember(final byte[] s
, final int offset
)
273 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++) {
274 // search for path component terminator
278 p
= binarySearch(contents
, s
, offset
, slash
);
280 final TreeEntry r
= contents
[p
];
281 if (slash
< s
.length
)
282 return r
instanceof Tree ?
((Tree
) r
).findMember(s
, slash
+ 1)
289 public void accept(final TreeVisitor tv
, final int flags
)
293 if ((MODIFIED_ONLY
& flags
) == MODIFIED_ONLY
&& !isModified())
296 if ((LOADED_ONLY
& flags
) == LOADED_ONLY
&& !isLoaded()) {
297 tv
.startVisitTree(this);
298 tv
.endVisitTree(this);
303 tv
.startVisitTree(this);
305 if ((CONCURRENT_MODIFICATION
& flags
) == CONCURRENT_MODIFICATION
)
310 for (int k
= 0; k
< c
.length
; k
++)
311 c
[k
].accept(tv
, flags
);
313 tv
.endVisitTree(this);
316 private void ensureLoaded() throws IOException
, MissingObjectException
{
318 final ObjectLoader or
= db
.openTree(getId());
320 throw new MissingObjectException(getId(), Constants
.TYPE_TREE
);
321 readTree(or
.getBytes());
325 private void readTree(final byte[] raw
) throws IOException
{
327 TreeEntry
[] temp
= new TreeEntry
[64];
329 boolean resort
= false;
331 while (rawPtr
< raw
.length
) {
332 int c
= raw
[rawPtr
++] & 0xff;
333 if (c
< '0' || c
> '7')
334 throw new CorruptObjectException(getId(), "invalid entry mode");
337 c
= raw
[rawPtr
++] & 0xff;
340 else if (c
< '0' || c
> '7')
341 throw new CorruptObjectException(getId(), "invalid mode");
347 while ((raw
[rawPtr
+ nameLen
] & 0xff) != 0)
349 final byte[] name
= new byte[nameLen
];
350 System
.arraycopy(raw
, rawPtr
, name
, 0, nameLen
);
351 rawPtr
+= nameLen
+ 1;
353 final byte[] entId
= new byte[Constants
.OBJECT_ID_LENGTH
];
354 System
.arraycopy(raw
, rawPtr
, entId
, 0, Constants
.OBJECT_ID_LENGTH
);
355 final ObjectId id
= new ObjectId(entId
);
356 rawPtr
+= Constants
.OBJECT_ID_LENGTH
;
359 if (FileMode
.REGULAR_FILE
.equals(mode
))
360 ent
= new FileTreeEntry(this, id
, name
, false);
361 else if (FileMode
.EXECUTABLE_FILE
.equals(mode
))
362 ent
= new FileTreeEntry(this, id
, name
, true);
363 else if (FileMode
.TREE
.equals(mode
)) {
364 ent
= new Tree(this, id
, name
);
366 } else if (FileMode
.SYMLINK
.equals(mode
))
367 ent
= new SymlinkTreeEntry(this, id
, name
);
369 throw new CorruptObjectException(getId(), "Invalid mode: "
370 + Integer
.toOctalString(mode
));
372 if (nextIndex
== temp
.length
) {
373 final TreeEntry
[] n
= new TreeEntry
[temp
.length
<< 1];
374 for (int k
= nextIndex
- 1; k
>= 0; k
--)
379 temp
[nextIndex
++] = ent
;
382 if (nextIndex
== temp
.length
)
385 final TreeEntry
[] n
= new TreeEntry
[nextIndex
];
386 for (int k
= nextIndex
- 1; k
>= 0; k
--)
391 // Resort contents using our internal sorting order. Git sorts
392 // subtrees as though their names end in '/' but that's not how
393 // we sort them in memory. Its all the fault of the index...
396 Arrays
.sort(contents
);
399 public String
toString() {
400 final StringBuffer r
= new StringBuffer();
401 r
.append(ObjectId
.toString(getId()));
403 r
.append(getFullName());