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
.ByteArrayOutputStream
;
20 import java
.io
.IOException
;
21 import java
.io
.InputStream
;
22 import java
.util
.Arrays
;
24 import org
.spearce
.jgit
.errors
.CorruptObjectException
;
25 import org
.spearce
.jgit
.errors
.EntryExistsException
;
26 import org
.spearce
.jgit
.errors
.MissingObjectException
;
28 public class Tree
extends TreeEntry
implements Treeish
30 public static final TreeEntry
[] EMPTY_TREE
= {};
32 public static final int compareNames(final byte[] a
, final byte[] b
)
34 return compareNames(a
, b
, 0, b
.length
);
37 public static final int compareNames(
39 final byte[] nameUTF8
,
43 for (int j
= 0, k
= nameStart
; j
< a
.length
&& k
< nameEnd
; j
++, k
++)
45 final int aj
= a
[j
] & 0xff;
46 final int bk
= nameUTF8
[k
] & 0xff;
53 final int namelength
= nameEnd
- nameStart
;
54 if (a
.length
== namelength
)
56 else if (a
.length
< namelength
)
62 private static final byte[] substring(
67 if (nameStart
== 0 && nameStart
== s
.length
)
72 final byte[] n
= new byte[nameEnd
- nameStart
];
73 System
.arraycopy(s
, nameStart
, n
, 0, n
.length
);
77 private static final int binarySearch(
78 final TreeEntry
[] entries
,
79 final byte[] nameUTF8
,
83 if (entries
.length
== 0)
85 int high
= entries
.length
;
89 final int mid
= (low
+ high
) / 2;
90 final int cmp
= compareNames(
91 entries
[mid
].getNameUTF8(),
112 private final Repository db
;
114 private TreeEntry
[] contents
;
116 public Tree(final Repository repo
)
118 super(null, null, null);
120 contents
= EMPTY_TREE
;
123 public Tree(final Repository repo
, final ObjectId myId
, final InputStream is
)
126 super(null, myId
, null);
131 private Tree(final Tree parent
, final byte[] nameUTF8
)
133 super(parent
, null, nameUTF8
);
134 db
= parent
.getRepository();
135 contents
= EMPTY_TREE
;
138 public Tree(final Repository r
, final ObjectId id
, final byte[] nameUTF8
)
140 super(null, id
, nameUTF8
);
144 public Tree(final Tree parent
, final ObjectId id
, final byte[] nameUTF8
)
146 super(parent
, id
, nameUTF8
);
147 db
= parent
.getRepository();
150 public FileMode
getMode()
152 return FileMode
.TREE
;
155 public boolean isRoot()
157 return getParent() == null;
160 public Repository
getRepository()
165 public final ObjectId
getTreeId()
170 public final Tree
getTree()
175 public boolean isLoaded()
177 return contents
!= null;
184 throw new IllegalStateException("Cannot unload a modified tree.");
189 public FileTreeEntry
addFile(final String name
) throws IOException
191 return addFile(name
.getBytes(Constants
.CHARACTER_ENCODING
), 0);
194 public FileTreeEntry
addFile(final byte[] s
, final int offset
)
200 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++)
201 /* search for path component terminator */;
204 p
= binarySearch(contents
, s
, offset
, slash
);
205 if (p
>= 0 && slash
< s
.length
&& contents
[p
] instanceof Tree
)
207 return ((Tree
) contents
[p
]).addFile(s
, slash
+ 1);
210 final byte[] newName
= substring(s
, offset
, slash
);
213 throw new EntryExistsException(new String(
215 Constants
.CHARACTER_ENCODING
));
217 else if (slash
< s
.length
)
219 final Tree t
= new Tree(this, newName
);
221 return t
.addFile(s
, slash
+ 1);
225 final FileTreeEntry f
= new FileTreeEntry(
235 public Tree
addTree(final String name
) throws IOException
237 return addTree(name
.getBytes(Constants
.CHARACTER_ENCODING
), 0);
240 public Tree
addTree(final byte[] s
, final int offset
) throws IOException
245 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++)
246 /* search for path component terminator */;
249 p
= binarySearch(contents
, s
, offset
, slash
);
250 if (p
>= 0 && slash
< s
.length
&& contents
[p
] instanceof Tree
)
252 return ((Tree
) contents
[p
]).addTree(s
, slash
+ 1);
255 final byte[] newName
= substring(s
, offset
, slash
);
258 throw new EntryExistsException(new String(
260 Constants
.CHARACTER_ENCODING
));
263 final Tree t
= new Tree(this, newName
);
265 return slash
== s
.length ? t
: t
.addTree(s
, slash
+ 1);
268 public void addEntry(final TreeEntry e
) throws IOException
273 p
= binarySearch(contents
, e
.getNameUTF8(), 0, e
.getNameUTF8().length
);
276 e
.attachParent(this);
281 throw new EntryExistsException(new String(
283 Constants
.CHARACTER_ENCODING
));
287 private void insertEntry(int p
, final TreeEntry e
)
289 final TreeEntry
[] c
= contents
;
290 final TreeEntry
[] n
= new TreeEntry
[c
.length
+ 1];
292 for (int k
= c
.length
- 1; k
>= p
; k
--)
297 for (int k
= p
- 1; k
>= 0; k
--)
305 void removeEntry(final TreeEntry e
)
307 final TreeEntry
[] c
= contents
;
308 final int p
= binarySearch(
312 e
.getNameUTF8().length
);
315 final TreeEntry
[] n
= new TreeEntry
[c
.length
- 1];
316 for (int k
= c
.length
- 1; k
> p
; k
--)
320 for (int k
= p
- 1; k
>= 0; k
--)
329 public int memberCount() throws IOException
332 return contents
.length
;
335 public TreeEntry
[] members() throws IOException
338 final TreeEntry
[] c
= contents
;
341 final TreeEntry
[] r
= new TreeEntry
[c
.length
];
342 for (int k
= c
.length
- 1; k
>= 0; k
--)
354 public boolean exists(final String s
) throws IOException
356 return findMember(s
) != null;
359 public TreeEntry
findMember(final String s
) throws IOException
361 return findMember(s
.getBytes(Constants
.CHARACTER_ENCODING
), 0);
364 public TreeEntry
findMember(final byte[] s
, final int offset
)
370 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++)
371 /* search for path component terminator */;
374 p
= binarySearch(contents
, s
, offset
, slash
);
377 final TreeEntry r
= contents
[p
];
378 if (slash
< s
.length
)
380 return r
instanceof Tree
381 ?
((Tree
) r
).findMember(s
, slash
+ 1)
389 public void accept(final TreeVisitor tv
, final int flags
)
394 if ((MODIFIED_ONLY
& flags
) == MODIFIED_ONLY
&& !isModified())
399 if ((LOADED_ONLY
& flags
) == LOADED_ONLY
&& !isLoaded())
401 tv
.startVisitTree(this);
402 tv
.endVisitTree(this);
407 tv
.startVisitTree(this);
409 if ((CONCURRENT_MODIFICATION
& flags
) == CONCURRENT_MODIFICATION
)
418 for (int k
= 0; k
< c
.length
; k
++)
420 c
[k
].accept(tv
, flags
);
423 tv
.endVisitTree(this);
426 private void ensureLoaded() throws IOException
, MissingObjectException
430 final ObjectReader or
= db
.openTree(getId());
433 throw new MissingObjectException(getId(), Constants
.TYPE_TREE
);
437 readTree(or
.getInputStream());
446 private void readTree(final InputStream is
) throws IOException
448 TreeEntry
[] temp
= new TreeEntry
[64];
455 final ByteArrayOutputStream nameBuf
;
467 else if (c
< '0' || c
> '7')
469 throw new CorruptObjectException(getId(), "invalid entry mode");
479 else if (c
< '0' || c
> '7')
481 throw new CorruptObjectException(getId(), "invalid mode");
487 nameBuf
= new ByteArrayOutputStream(128);
493 throw new CorruptObjectException(getId(), "unexpected eof");
502 entId
= new byte[Constants
.OBJECT_ID_LENGTH
];
504 while ((c
= is
.read(entId
, entIdLen
, entId
.length
- entIdLen
)) > 0)
508 if (entIdLen
!= entId
.length
)
510 throw new CorruptObjectException(getId(), "missing hash");
513 id
= new ObjectId(entId
);
514 name
= nameBuf
.toByteArray();
516 if (FileMode
.REGULAR_FILE
.equals(mode
))
518 ent
= new FileTreeEntry(this, id
, name
, false);
520 else if (FileMode
.EXECUTABLE_FILE
.equals(mode
))
522 ent
= new FileTreeEntry(this, id
, name
, true);
524 else if (FileMode
.TREE
.equals(mode
))
526 ent
= new Tree(this, id
, name
);
528 else if (FileMode
.SYMLINK
.equals(mode
))
530 ent
= new SymlinkTreeEntry(this, id
, name
);
534 throw new CorruptObjectException(getId(), "Invalid mode: "
535 + Integer
.toOctalString(mode
));
538 if (nextIndex
== temp
.length
)
540 final TreeEntry
[] n
= new TreeEntry
[temp
.length
<< 1];
541 for (int k
= nextIndex
- 1; k
>= 0; k
--)
548 temp
[nextIndex
++] = ent
;
551 if (nextIndex
== temp
.length
)
557 final TreeEntry
[] n
= new TreeEntry
[nextIndex
];
558 for (int k
= nextIndex
- 1; k
>= 0; k
--)
565 // Resort contents using our internal sorting order. GIT sorts
566 // subtrees as though their names end in '/' but that's not how
567 // we sort them in memory. Its all the fault of the index...
569 Arrays
.sort(contents
);
572 public String
toString()
574 final StringBuffer r
= new StringBuffer();
575 r
.append(ObjectId
.toString(getId()));
577 r
.append(getFullName());