Corrected tree entry sorting.
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / lib / Tree.java
bloba2636ddaa175aec5f87fce69b9921bfc4a38ea32
1 /*
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(
38 final byte[] a,
39 final byte[] nameUTF8,
40 final int nameStart,
41 final int nameEnd)
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;
47 if (aj < bk)
48 return -1;
49 else if (aj > bk)
50 return 1;
53 final int namelength = nameEnd - nameStart;
54 if (a.length == namelength)
55 return 0;
56 else if (a.length < namelength)
57 return -1;
58 else
59 return 1;
62 private static final byte[] substring(
63 final byte[] s,
64 final int nameStart,
65 final int nameEnd)
67 if (nameStart == 0 && nameStart == s.length)
69 return s;
72 final byte[] n = new byte[nameEnd - nameStart];
73 System.arraycopy(s, nameStart, n, 0, n.length);
74 return n;
77 private static final int binarySearch(
78 final TreeEntry[] entries,
79 final byte[] nameUTF8,
80 final int nameStart,
81 final int nameEnd)
83 if (entries.length == 0)
84 return -1;
85 int high = entries.length;
86 int low = 0;
89 final int mid = (low + high) / 2;
90 final int cmp = compareNames(
91 entries[mid].getNameUTF8(),
92 nameUTF8,
93 nameStart,
94 nameEnd);
95 if (cmp < 0)
97 low = mid + 1;
99 else if (cmp == 0)
101 return mid;
103 else
105 high = mid;
108 while (low < high);
109 return -(low + 1);
112 private final Repository db;
114 private TreeEntry[] contents;
116 public Tree(final Repository repo)
118 super(null, null, null);
119 db = repo;
120 contents = EMPTY_TREE;
123 public Tree(final Repository repo, final ObjectId myId, final InputStream is)
124 throws IOException
126 super(null, myId, null);
127 db = repo;
128 readTree(is);
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);
141 db = r;
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()
162 return db;
165 public final ObjectId getTreeId()
167 return getId();
170 public final Tree getTree()
172 return this;
175 public boolean isLoaded()
177 return contents != null;
180 public void unload()
182 if (isModified())
184 throw new IllegalStateException("Cannot unload a modified tree.");
186 contents = null;
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)
195 throws IOException
197 int slash;
198 int p;
200 for (slash = offset; slash < s.length && s[slash] != '/'; slash++)
201 /* search for path component terminator */;
203 ensureLoaded();
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);
211 if (p >= 0)
213 throw new EntryExistsException(new String(
214 newName,
215 Constants.CHARACTER_ENCODING));
217 else if (slash < s.length)
219 final Tree t = new Tree(this, newName);
220 insertEntry(p, t);
221 return t.addFile(s, slash + 1);
223 else
225 final FileTreeEntry f = new FileTreeEntry(
226 this,
227 null,
228 newName,
229 false);
230 insertEntry(p, f);
231 return f;
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
242 int slash;
243 int p;
245 for (slash = offset; slash < s.length && s[slash] != '/'; slash++)
246 /* search for path component terminator */;
248 ensureLoaded();
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);
256 if (p >= 0)
258 throw new EntryExistsException(new String(
259 newName,
260 Constants.CHARACTER_ENCODING));
263 final Tree t = new Tree(this, newName);
264 insertEntry(p, t);
265 return slash == s.length ? t : t.addTree(s, slash + 1);
268 public void addEntry(final TreeEntry e) throws IOException
270 final int p;
272 ensureLoaded();
273 p = binarySearch(contents, e.getNameUTF8(), 0, e.getNameUTF8().length);
274 if (p < 0)
276 e.attachParent(this);
277 insertEntry(p, e);
279 else
281 throw new EntryExistsException(new String(
282 e.getNameUTF8(),
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];
291 p = -(p + 1);
292 for (int k = c.length - 1; k >= p; k--)
294 n[k + 1] = c[k];
296 n[p] = e;
297 for (int k = p - 1; k >= 0; k--)
299 n[k] = c[k];
301 contents = n;
302 setModified();
305 void removeEntry(final TreeEntry e)
307 final TreeEntry[] c = contents;
308 final int p = binarySearch(
310 e.getNameUTF8(),
312 e.getNameUTF8().length);
313 if (p >= 0)
315 final TreeEntry[] n = new TreeEntry[c.length - 1];
316 for (int k = c.length - 1; k > p; k--)
318 n[k - 1] = c[k];
320 for (int k = p - 1; k >= 0; k--)
322 n[k] = c[k];
324 contents = n;
325 setModified();
329 public int memberCount() throws IOException
331 ensureLoaded();
332 return contents.length;
335 public TreeEntry[] members() throws IOException
337 ensureLoaded();
338 final TreeEntry[] c = contents;
339 if (c.length != 0)
341 final TreeEntry[] r = new TreeEntry[c.length];
342 for (int k = c.length - 1; k >= 0; k--)
344 r[k] = c[k];
346 return r;
348 else
350 return c;
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)
365 throws IOException
367 int slash;
368 int p;
370 for (slash = offset; slash < s.length && s[slash] != '/'; slash++)
371 /* search for path component terminator */;
373 ensureLoaded();
374 p = binarySearch(contents, s, offset, slash);
375 if (p >= 0)
377 final TreeEntry r = contents[p];
378 if (slash < s.length)
380 return r instanceof Tree
381 ? ((Tree) r).findMember(s, slash + 1)
382 : null;
384 return r;
386 return null;
389 public void accept(final TreeVisitor tv, final int flags)
390 throws IOException
392 final TreeEntry[] c;
394 if ((MODIFIED_ONLY & flags) == MODIFIED_ONLY && !isModified())
396 return;
399 if ((LOADED_ONLY & flags) == LOADED_ONLY && !isLoaded())
401 tv.startVisitTree(this);
402 tv.endVisitTree(this);
403 return;
406 ensureLoaded();
407 tv.startVisitTree(this);
409 if ((CONCURRENT_MODIFICATION & flags) == CONCURRENT_MODIFICATION)
411 c = members();
413 else
415 c = contents;
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
428 if (!isLoaded())
430 final ObjectReader or = db.openTree(getId());
431 if (or == null)
433 throw new MissingObjectException(getId(), Constants.TYPE_TREE);
437 readTree(or.getInputStream());
439 finally
441 or.close();
446 private void readTree(final InputStream is) throws IOException
448 TreeEntry[] temp = new TreeEntry[64];
449 int nextIndex = 0;
451 for (;;)
453 int c;
454 int mode;
455 final ByteArrayOutputStream nameBuf;
456 final byte[] entId;
457 final byte[] name;
458 final ObjectId id;
459 final TreeEntry ent;
460 int entIdLen;
462 c = is.read();
463 if (c == -1)
465 break;
467 else if (c < '0' || c > '7')
469 throw new CorruptObjectException(getId(), "invalid entry mode");
471 mode = c - '0';
472 for (;;)
474 c = is.read();
475 if (' ' == c)
477 break;
479 else if (c < '0' || c > '7')
481 throw new CorruptObjectException(getId(), "invalid mode");
483 mode *= 8;
484 mode += c - '0';
487 nameBuf = new ByteArrayOutputStream(128);
488 for (;;)
490 c = is.read();
491 if (c == -1)
493 throw new CorruptObjectException(getId(), "unexpected eof");
495 else if (0 == c)
497 break;
499 nameBuf.write(c);
502 entId = new byte[Constants.OBJECT_ID_LENGTH];
503 entIdLen = 0;
504 while ((c = is.read(entId, entIdLen, entId.length - entIdLen)) > 0)
506 entIdLen += c;
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);
532 else
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--)
543 n[k] = temp[k];
545 temp = n;
548 temp[nextIndex++] = ent;
551 if (nextIndex == temp.length)
553 contents = temp;
555 else
557 final TreeEntry[] n = new TreeEntry[nextIndex];
558 for (int k = nextIndex - 1; k >= 0; k--)
560 n[k] = temp[k];
562 contents = n;
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()));
576 r.append(" T ");
577 r.append(getFullName());
578 return r.toString();