--- /dev/null
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.notes;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+
+/**
+ * A note tree holding only note subtrees, each named using a 2 digit hex name.
+ *
+ * The fanout buckets/trees contain on average 256 subtrees, naming the subtrees
+ * by a slice of the ObjectId contained within them, from "00" through "ff".
+ *
+ * Each fanout bucket has a {@link #prefixLen} that defines how many digits it
+ * skips in an ObjectId before it gets to the digits matching {@link #table}.
+ *
+ * The root tree has {@code prefixLen == 0}, and thus does not skip any digits.
+ * For ObjectId "c0ffee...", the note (if it exists) will be stored within the
+ * bucket {@code table[0xc0]}.
+ *
+ * The first level tree has {@code prefixLen == 2}, and thus skips the first two
+ * digits. For the same example "c0ffee..." object, its note would be found
+ * within the {@code table[0xff]} bucket (as first 2 digits "c0" are skipped).
+ *
+ * Each subtree is loaded on-demand, reducing startup latency for reads that
+ * only need to examine a few objects. However, due to the rather uniform
+ * distribution of the SHA-1 hash that is used for ObjectIds, accessing 256
+ * objects is very likely to load all of the subtrees into memory.
+ *
+ * A FanoutBucket must be parsed from a tree object by {@link NoteParser}.
+ */
+class FanoutBucket extends InMemoryNoteBucket {
+ /**
+ * Fan-out table similar to the PackIndex structure.
+ *
+ * Notes for an object are stored within the sub-bucket that is held here as
+ * {@code table[ objectId.getByte( prefixLen / 2 ) ]}. If the slot is null
+ * there are no notes with that prefix.
+ */
+ private final NoteBucket[] table;
+
+ FanoutBucket(int prefixLen) {
+ super(prefixLen);
+ table = new NoteBucket[256];
+ }
+
+ void parseOneEntry(int cell, ObjectId id) {
+ table[cell] = new LazyNoteBucket(id);
+ }
+
+ @Override
+ ObjectId get(AnyObjectId objId, ObjectReader or) throws IOException {
+ NoteBucket b = table[cell(objId)];
+ return b != null ? b.get(objId, or) : null;
+ }
+
+ private int cell(AnyObjectId id) {
+ return id.getByte(prefixLen >> 1);
+ }
+
+ private class LazyNoteBucket extends NoteBucket {
+ private final ObjectId treeId;
+
+ LazyNoteBucket(ObjectId treeId) {
+ this.treeId = treeId;
+ }
+
+ @Override
+ ObjectId get(AnyObjectId objId, ObjectReader or) throws IOException {
+ return load(objId, or).get(objId, or);
+ }
+
+ private NoteBucket load(AnyObjectId objId, ObjectReader or)
+ throws IOException {
+ AbbreviatedObjectId p = objId.abbreviate(prefixLen + 2);
+ NoteBucket self = NoteParser.parse(p, treeId, or);
+ table[cell(objId)] = self;
+ return self;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.notes;
+
+/** A note bucket that has been loaded into the process. */
+abstract class InMemoryNoteBucket extends NoteBucket {
+ /**
+ * Number of leading digits that leads to this bucket in the note path.
+ *
+ * This is counted in terms of hex digits, not raw bytes. Each bucket level
+ * is typically 2 higher than its parent, placing about 256 items in each
+ * level of the tree.
+ */
+ final int prefixLen;
+
+ InMemoryNoteBucket(int prefixLen) {
+ this.prefixLen = prefixLen;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.notes;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+
+/**
+ * A note tree holding only notes, with no subtrees.
+ *
+ * The leaf bucket contains on average less than 256 notes, all of whom share
+ * the same leading prefix. If a notes branch has less than 256 notes, the top
+ * level tree of the branch should be a LeafBucket. Once a notes branch has more
+ * than 256 notes, the root should be a {@link FanoutBucket} and the LeafBucket
+ * will appear only as a cell of a FanoutBucket.
+ *
+ * Entries within the LeafBucket are stored sorted by ObjectId, and lookup is
+ * performed using binary search. As the entry list should contain fewer than
+ * 256 elements, the average number of compares to find an element should be
+ * less than 8 due to the O(log N) lookup behavior.
+ *
+ * A LeafBucket must be parsed from a tree object by {@link NoteParser}.
+ */
+class LeafBucket extends InMemoryNoteBucket {
+ /** All note blobs in this bucket, sorted sequentially. */
+ private Note[] notes;
+
+ /** Number of items in {@link #notes}. */
+ private int cnt;
+
+ LeafBucket(int prefixLen) {
+ super(prefixLen);
+ notes = new Note[4];
+ }
+
+ private int search(AnyObjectId objId) {
+ int low = 0;
+ int high = cnt;
+ while (low < high) {
+ int mid = (low + high) >>> 1;
+ int cmp = objId.compareTo(notes[mid]);
+ if (cmp < 0)
+ high = mid;
+ else if (cmp == 0)
+ return mid;
+ else
+ low = mid + 1;
+ }
+ return -(low + 1);
+ }
+
+ ObjectId get(AnyObjectId objId, ObjectReader or) {
+ int idx = search(objId);
+ return 0 <= idx ? notes[idx].getData() : null;
+ }
+
+ void parseOneEntry(AnyObjectId noteOn, AnyObjectId noteData) {
+ growIfFull();
+ notes[cnt++] = new Note(noteOn, noteData.copy());
+ }
+
+ private void growIfFull() {
+ if (notes.length == cnt) {
+ Note[] n = new Note[notes.length * 2];
+ System.arraycopy(notes, 0, n, 0, cnt);
+ notes = n;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.notes;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+
+/**
+ * A tree that stores note objects.
+ *
+ * @see FanoutBucket
+ * @see LeafBucket
+ */
+abstract class NoteBucket {
+ abstract ObjectId get(AnyObjectId objId, ObjectReader reader)
+ throws IOException;
+}
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdSubclassMap;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.util.RawParseUtils;
/**
* Index of notes from a note branch.
*
* This class is not thread-safe, and relies on an {@link ObjectReader} that it
- * borrows/shares with the caller.
- *
- * The current implementation is not very efficient. During load it recursively
- * scans the entire note branch and indexes all annotated objects. If there are
- * more than 256 notes in the branch, and not all of them will be accessed by
- * the caller, this aggressive up-front loading probably takes too much time.
+ * borrows/shares with the caller. The reader can be used during any call, and
+ * is not released by this class. The caller should arrange for releasing the
+ * shared {@code ObjectReader} at the proper times.
*/
public class NoteMap {
/**
private final ObjectReader reader;
/** All of the notes that have been loaded. */
- private ObjectIdSubclassMap<Note> notes = new ObjectIdSubclassMap<Note>();
+ private InMemoryNoteBucket root;
private NoteMap(ObjectReader reader) {
this.reader = reader;
* @param id
* the object to look for.
* @return the note's blob ObjectId, or null if no note exists.
+ * @throws IOException
+ * a portion of the note space is not accessible.
*/
- public ObjectId get(AnyObjectId id) {
- Note note = notes.get(id);
- return note != null ? note.getData() : null;
+ public ObjectId get(AnyObjectId id) throws IOException {
+ return root.get(id, reader);
}
/**
* @param id
* the object to look for.
* @return true if a note exists; false if there is no note.
+ * @throws IOException
+ * a portion of the note space is not accessible.
*/
- public boolean contains(AnyObjectId id) {
+ public boolean contains(AnyObjectId id) throws IOException {
return get(id) != null;
}
return null;
}
- private void load(ObjectId treeId) throws MissingObjectException,
+ private void load(ObjectId rootTree) throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException {
- try {
- byte[] idBuf = new byte[Constants.OBJECT_ID_LENGTH];
- TreeWalk tw = new TreeWalk(reader);
- tw.reset();
- tw.setRecursive(true);
- tw.addTree(treeId);
-
- notes.clear();
-
- while (tw.next()) {
- ObjectId pathId = pathToId(idBuf, tw.getRawPath());
- if (pathId != null)
- notes.add(new Note(pathId, tw.getObjectId(0)));
- }
- } finally {
- reader.release();
- }
- }
-
- private static ObjectId pathToId(byte[] rawIdBuf, byte[] path) {
- int r = 0;
-
- for (int i = 0; i < path.length; i++) {
- byte c = path[i];
-
- // Skip embedded '/' in paths, these aren't part of a name.
- if (c == '/')
- continue;
-
- // We need at least 2 digits to consume in this cycle.
- if (path.length < i + 2)
- return null;
-
- // We cannot exceed an ObjectId raw length.
- if (r == Constants.OBJECT_ID_STRING_LENGTH)
- return null;
-
- int high, low;
- try {
- high = RawParseUtils.parseHexInt4(c);
- low = RawParseUtils.parseHexInt4(path[++i]);
- } catch (ArrayIndexOutOfBoundsException notHex) {
- return null;
- }
-
- rawIdBuf[r++] = (byte) ((high << 4) | low);
- }
-
- // We need exactly the right number of input bytes.
- if (r == Constants.OBJECT_ID_LENGTH)
- return ObjectId.fromRaw(rawIdBuf);
- else
- return null;
+ AbbreviatedObjectId none = AbbreviatedObjectId.fromString("");
+ root = NoteParser.parse(none, rootTree, reader);
}
}
--- /dev/null
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.notes;
+
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+import static org.eclipse.jgit.lib.FileMode.TREE;
+import static org.eclipse.jgit.util.RawParseUtils.parseHexInt4;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+
+/** Custom tree parser to select note bucket type and load it. */
+final class NoteParser extends CanonicalTreeParser {
+ /**
+ * Parse a tree object into a {@link NoteBucket} instance.
+ *
+ * The type of note tree is automatically detected by examining the items
+ * within the tree, and allocating the proper storage type based on the
+ * first note-like entry encountered. Since the method parses by guessing
+ * the type on the first element, malformed note trees can be read as the
+ * wrong type of tree.
+ *
+ * This method is not recursive, it parses the one tree given to it and
+ * returns the bucket. If there are subtrees for note storage, they are
+ * setup as lazy pointers that will be resolved at a later time.
+ *
+ * @param prefix
+ * common hex digits that all notes within this tree share. The
+ * root tree has {@code prefix.length() == 0}, the first-level
+ * subtrees should be {@code prefix.length()==2}, etc.
+ * @param treeId
+ * the tree to read from the repository.
+ * @param reader
+ * reader to access the tree object.
+ * @return bucket to holding the notes of the specified tree.
+ * @throws IOException
+ * {@code treeId} cannot be accessed.
+ */
+ static InMemoryNoteBucket parse(AbbreviatedObjectId prefix,
+ final ObjectId treeId, final ObjectReader reader)
+ throws IOException {
+ return new NoteParser(prefix, reader, treeId).parseTree();
+ }
+
+ private final AbbreviatedObjectId prefix;
+
+ private final int pathPadding;
+
+ private NoteParser(AbbreviatedObjectId p, ObjectReader r, ObjectId t)
+ throws IncorrectObjectTypeException, IOException {
+ super(encodeASCII(p.name()), r, t);
+ prefix = p;
+
+ // Our path buffer has a '/' that we don't want after the prefix.
+ // Drop it by shifting the path down one position.
+ pathPadding = 0 < prefix.length() ? 1 : 0;
+ if (0 < pathPadding)
+ System.arraycopy(path, 0, path, pathPadding, prefix.length());
+ }
+
+ private InMemoryNoteBucket parseTree() {
+ for (; !eof(); next(1)) {
+ if (pathLen == pathPadding + OBJECT_ID_STRING_LENGTH && isHex())
+ return parseLeafTree();
+
+ else if (getNameLength() == 2 && isHex() && isTree())
+ return parseFanoutTree();
+ }
+
+ // If we cannot determine the style used, assume its a leaf.
+ return new LeafBucket(prefix.length());
+ }
+
+ private LeafBucket parseLeafTree() {
+ final LeafBucket leaf = new LeafBucket(prefix.length());
+ final MutableObjectId idBuf = new MutableObjectId();
+
+ for (; !eof(); next(1)) {
+ if (parseObjectId(idBuf))
+ leaf.parseOneEntry(idBuf, getEntryObjectId());
+ }
+
+ return leaf;
+ }
+
+ private boolean parseObjectId(MutableObjectId id) {
+ if (pathLen == pathPadding + OBJECT_ID_STRING_LENGTH) {
+ try {
+ id.fromString(path, pathPadding);
+ return true;
+ } catch (ArrayIndexOutOfBoundsException notHex) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private FanoutBucket parseFanoutTree() {
+ final FanoutBucket fanout = new FanoutBucket(prefix.length());
+
+ for (; !eof(); next(1)) {
+ final int cell = parseFanoutCell();
+ if (0 <= cell)
+ fanout.parseOneEntry(cell, getEntryObjectId());
+ }
+
+ return fanout;
+ }
+
+ private int parseFanoutCell() {
+ if (getNameLength() == 2 && isTree()) {
+ try {
+ return (parseHexInt4(path[pathOffset + 0]) << 4)
+ | parseHexInt4(path[pathOffset + 1]);
+ } catch (ArrayIndexOutOfBoundsException notHex) {
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+ }
+
+ private boolean isTree() {
+ return TREE.equals(mode);
+ }
+
+ private boolean isHex() {
+ try {
+ for (int i = pathOffset; i < pathLen; i++)
+ parseHexInt4(path[i]);
+ return true;
+ } catch (ArrayIndexOutOfBoundsException fail) {
+ return false;
+ }
+ }
+}