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 General Public
6 * License, version 2, 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 * General Public License for more details.
13 * You should have received a copy of the GNU 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
;
21 import org
.spearce
.jgit
.errors
.CorruptObjectException
;
22 import org
.spearce
.jgit
.errors
.EntryExistsException
;
23 import org
.spearce
.jgit
.errors
.MissingObjectException
;
25 public class Tree
extends TreeEntry
implements Treeish
{
26 public static final TreeEntry
[] EMPTY_TREE
= {};
28 public static final int compareNames(final byte[] a
, final byte[] b
, final int lasta
,final int lastb
) {
29 return compareNames(a
, b
, 0, b
.length
, lasta
, lastb
);
32 public static final int compareNames(final byte[] a
, final byte[] nameUTF8
,
33 final int nameStart
, final int nameEnd
, final int lasta
, int lastb
) {
35 for (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;
53 int bk
= nameUTF8
[k
] & 0xff;
63 else if (lasta
> lastb
)
66 final int namelength
= nameEnd
- nameStart
;
67 if (a
.length
== namelength
)
69 else if (a
.length
< namelength
)
75 private static final byte[] substring(final byte[] s
, final int nameStart
,
77 if (nameStart
== 0 && nameStart
== s
.length
)
79 final byte[] n
= new byte[nameEnd
- nameStart
];
80 System
.arraycopy(s
, nameStart
, n
, 0, n
.length
);
84 private static final int binarySearch(final TreeEntry
[] entries
,
85 final byte[] nameUTF8
, final int nameUTF8last
, final int nameStart
, final int nameEnd
) {
86 if (entries
.length
== 0)
88 int high
= entries
.length
;
91 final int mid
= (low
+ high
) / 2;
92 final int cmp
= compareNames(entries
[mid
].getNameUTF8(), nameUTF8
,
93 nameStart
, nameEnd
, TreeEntry
.lastChar(entries
[mid
]), nameUTF8last
);
100 } while (low
< high
);
104 private final Repository db
;
106 private TreeEntry
[] contents
;
108 public Tree(final Repository repo
) {
109 super(null, null, null);
111 contents
= EMPTY_TREE
;
114 public Tree(final Repository repo
, final ObjectId myId
, final byte[] raw
)
116 super(null, myId
, null);
121 public Tree(final Tree parent
, final byte[] nameUTF8
) {
122 super(parent
, null, nameUTF8
);
123 db
= parent
.getRepository();
124 contents
= EMPTY_TREE
;
127 public Tree(final Tree parent
, final ObjectId id
, final byte[] nameUTF8
) {
128 super(parent
, id
, nameUTF8
);
129 db
= parent
.getRepository();
132 public FileMode
getMode() {
133 return FileMode
.TREE
;
136 public boolean isRoot() {
137 return getParent() == null;
140 public Repository
getRepository() {
144 public final ObjectId
getTreeId() {
148 public final Tree
getTree() {
152 public boolean isLoaded() {
153 return contents
!= null;
156 public void unload() {
158 throw new IllegalStateException("Cannot unload a modified tree.");
162 public FileTreeEntry
addFile(final String name
) throws IOException
{
163 return addFile(Repository
.gitInternalSlash(name
.getBytes(Constants
.CHARACTER_ENCODING
)), 0);
166 public FileTreeEntry
addFile(final byte[] s
, final int offset
)
171 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++) {
172 // search for path component terminator
176 byte xlast
= slash
<s
.length ?
(byte)'/' : 0;
177 p
= binarySearch(contents
, s
, xlast
, offset
, slash
);
178 if (p
>= 0 && slash
< s
.length
&& contents
[p
] instanceof Tree
)
179 return ((Tree
) contents
[p
]).addFile(s
, slash
+ 1);
181 final byte[] newName
= substring(s
, offset
, slash
);
183 throw new EntryExistsException(new String(newName
,
184 Constants
.CHARACTER_ENCODING
));
185 else if (slash
< s
.length
) {
186 final Tree t
= new Tree(this, newName
);
188 return t
.addFile(s
, slash
+ 1);
190 final FileTreeEntry f
= new FileTreeEntry(this, null, newName
,
197 public Tree
addTree(final String name
) throws IOException
{
198 return addTree(Repository
.gitInternalSlash(name
.getBytes(Constants
.CHARACTER_ENCODING
)), 0);
201 public Tree
addTree(final byte[] s
, final int offset
) throws IOException
{
205 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++) {
206 // search for path component terminator
210 p
= binarySearch(contents
, s
, (byte)'/', offset
, slash
);
211 if (p
>= 0 && slash
< s
.length
&& contents
[p
] instanceof Tree
)
212 return ((Tree
) contents
[p
]).addTree(s
, slash
+ 1);
214 final byte[] newName
= substring(s
, offset
, slash
);
216 throw new EntryExistsException(new String(newName
,
217 Constants
.CHARACTER_ENCODING
));
219 final Tree t
= new Tree(this, newName
);
221 return slash
== s
.length ? t
: t
.addTree(s
, slash
+ 1);
224 public void addEntry(final TreeEntry e
) throws IOException
{
228 p
= binarySearch(contents
, e
.getNameUTF8(), TreeEntry
.lastChar(e
), 0, e
.getNameUTF8().length
);
230 e
.attachParent(this);
233 throw new EntryExistsException(new String(e
.getNameUTF8(),
234 Constants
.CHARACTER_ENCODING
));
238 private void insertEntry(int p
, final TreeEntry e
) {
239 final TreeEntry
[] c
= contents
;
240 final TreeEntry
[] n
= new TreeEntry
[c
.length
+ 1];
242 for (int k
= c
.length
- 1; k
>= p
; k
--)
245 for (int k
= p
- 1; k
>= 0; k
--)
251 void removeEntry(final TreeEntry e
) {
252 final TreeEntry
[] c
= contents
;
253 final int p
= binarySearch(c
, e
.getNameUTF8(), TreeEntry
.lastChar(e
), 0,
254 e
.getNameUTF8().length
);
256 final TreeEntry
[] n
= new TreeEntry
[c
.length
- 1];
257 for (int k
= c
.length
- 1; k
> p
; k
--)
259 for (int k
= p
- 1; k
>= 0; k
--)
266 public int memberCount() throws IOException
{
268 return contents
.length
;
271 public TreeEntry
[] members() throws IOException
{
273 final TreeEntry
[] c
= contents
;
275 final TreeEntry
[] r
= new TreeEntry
[c
.length
];
276 for (int k
= c
.length
- 1; k
>= 0; k
--)
283 public boolean exists(final String s
, byte slast
) throws IOException
{
284 return findMember(s
, slast
) != null;
287 public boolean existsTree(String path
) throws IOException
{
288 return exists(path
,(byte)'/');
291 public boolean existsBlob(String path
) throws IOException
{
292 return exists(path
,(byte)0);
295 public TreeEntry
findMember(final String s
, byte slast
) throws IOException
{
296 return findMember(Repository
.gitInternalSlash(s
.getBytes(Constants
.CHARACTER_ENCODING
)), slast
, 0);
299 public TreeEntry
findMember(final byte[] s
, final byte slast
, final int offset
)
304 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++) {
305 // search for path component terminator
309 byte xlast
= slash
<s
.length ?
(byte)'/' : slast
;
310 p
= binarySearch(contents
, s
, xlast
, offset
, slash
);
312 final TreeEntry r
= contents
[p
];
313 if (slash
< s
.length
-1)
314 return r
instanceof Tree ?
((Tree
) r
).findMember(s
, slast
, slash
+ 1)
321 public TreeEntry
findBlobMember(String s
) throws IOException
{
322 return findMember(s
,(byte)0);
325 public TreeEntry
findTreeMember(String s
) throws IOException
{
326 return findMember(s
,(byte)'/');
329 public void accept(final TreeVisitor tv
, final int flags
)
333 if ((MODIFIED_ONLY
& flags
) == MODIFIED_ONLY
&& !isModified())
336 if ((LOADED_ONLY
& flags
) == LOADED_ONLY
&& !isLoaded()) {
337 tv
.startVisitTree(this);
338 tv
.endVisitTree(this);
343 tv
.startVisitTree(this);
345 if ((CONCURRENT_MODIFICATION
& flags
) == CONCURRENT_MODIFICATION
)
350 for (int k
= 0; k
< c
.length
; k
++)
351 c
[k
].accept(tv
, flags
);
353 tv
.endVisitTree(this);
356 private void ensureLoaded() throws IOException
, MissingObjectException
{
358 final ObjectLoader or
= db
.openTree(getId());
360 throw new MissingObjectException(getId(), Constants
.TYPE_TREE
);
361 readTree(or
.getBytes());
365 private void readTree(final byte[] raw
) throws IOException
{
367 TreeEntry
[] temp
= new TreeEntry
[64];
370 while (rawPtr
< raw
.length
) {
371 int c
= raw
[rawPtr
++] & 0xff;
372 if (c
< '0' || c
> '7')
373 throw new CorruptObjectException(getId(), "invalid entry mode");
376 c
= raw
[rawPtr
++] & 0xff;
379 else if (c
< '0' || c
> '7')
380 throw new CorruptObjectException(getId(), "invalid mode");
386 while ((raw
[rawPtr
+ nameLen
] & 0xff) != 0)
388 final byte[] name
= new byte[nameLen
];
389 System
.arraycopy(raw
, rawPtr
, name
, 0, nameLen
);
390 rawPtr
+= nameLen
+ 1;
392 final byte[] entId
= new byte[Constants
.OBJECT_ID_LENGTH
];
393 System
.arraycopy(raw
, rawPtr
, entId
, 0, Constants
.OBJECT_ID_LENGTH
);
394 final ObjectId id
= new ObjectId(entId
);
395 rawPtr
+= Constants
.OBJECT_ID_LENGTH
;
398 if (FileMode
.REGULAR_FILE
.equals(mode
))
399 ent
= new FileTreeEntry(this, id
, name
, false);
400 else if (FileMode
.EXECUTABLE_FILE
.equals(mode
))
401 ent
= new FileTreeEntry(this, id
, name
, true);
402 else if (FileMode
.TREE
.equals(mode
)) {
403 ent
= new Tree(this, id
, name
);
404 } else if (FileMode
.SYMLINK
.equals(mode
))
405 ent
= new SymlinkTreeEntry(this, id
, name
);
407 throw new CorruptObjectException(getId(), "Invalid mode: "
408 + Integer
.toOctalString(mode
));
410 if (nextIndex
== temp
.length
) {
411 final TreeEntry
[] n
= new TreeEntry
[temp
.length
<< 1];
412 for (int k
= nextIndex
- 1; k
>= 0; k
--)
417 temp
[nextIndex
++] = ent
;
420 if (nextIndex
== temp
.length
)
423 final TreeEntry
[] n
= new TreeEntry
[nextIndex
];
424 for (int k
= nextIndex
- 1; k
>= 0; k
--)
431 public String
toString() {
432 final StringBuffer r
= new StringBuffer();
433 r
.append(ObjectId
.toString(getId()));
435 r
.append(getFullName());