123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- /*
- * 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.FileMode.TREE;
-
- import java.io.IOException;
- import java.util.Iterator;
- import java.util.NoSuchElementException;
-
- import org.eclipse.jgit.lib.AbbreviatedObjectId;
- import org.eclipse.jgit.lib.AnyObjectId;
- import org.eclipse.jgit.lib.MutableObjectId;
- import org.eclipse.jgit.lib.ObjectId;
- import org.eclipse.jgit.lib.ObjectInserter;
- import org.eclipse.jgit.lib.ObjectReader;
- import org.eclipse.jgit.lib.TreeFormatter;
-
- /**
- * 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;
-
- /** Number of non-null slots in {@link #table}. */
- private int cnt;
-
- FanoutBucket(int prefixLen) {
- super(prefixLen);
- table = new NoteBucket[256];
- }
-
- void setBucket(int cell, ObjectId id) {
- table[cell] = new LazyNoteBucket(id);
- cnt++;
- }
-
- void setBucket(int cell, InMemoryNoteBucket bucket) {
- table[cell] = bucket;
- cnt++;
- }
-
- @Override
- Note getNote(AnyObjectId objId, ObjectReader or) throws IOException {
- NoteBucket b = table[cell(objId)];
- return b != null ? b.getNote(objId, or) : null;
-
- }
-
- NoteBucket getBucket(int cell) {
- return table[cell];
- }
-
- static InMemoryNoteBucket loadIfLazy(NoteBucket b, AnyObjectId prefix,
- ObjectReader or) throws IOException {
- if (b == null)
- return null;
- if (b instanceof InMemoryNoteBucket)
- return (InMemoryNoteBucket) b;
- return ((LazyNoteBucket) b).load(prefix, or);
- }
-
- @Override
- Iterator<Note> iterator(AnyObjectId objId, final ObjectReader reader)
- throws IOException {
- final MutableObjectId id = new MutableObjectId();
- id.fromObjectId(objId);
-
- return new Iterator<Note>() {
- private int cell;
-
- private Iterator<Note> itr;
-
- public boolean hasNext() {
- if (itr != null && itr.hasNext())
- return true;
-
- for (; cell < table.length; cell++) {
- NoteBucket b = table[cell];
- if (b == null)
- continue;
-
- try {
- id.setByte(prefixLen >> 1, cell);
- itr = b.iterator(id, reader);
- } catch (IOException err) {
- throw new RuntimeException(err);
- }
-
- if (itr.hasNext()) {
- cell++;
- return true;
- }
- }
- return false;
- }
-
- public Note next() {
- if (hasNext())
- return itr.next();
- else
- throw new NoSuchElementException();
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
-
- @Override
- int estimateSize(AnyObjectId noteOn, ObjectReader or) throws IOException {
- // If most of this fan-out is full, estimate it should still be split.
- if (LeafBucket.MAX_SIZE * 3 / 4 <= cnt)
- return 1 + LeafBucket.MAX_SIZE;
-
- // Due to the uniform distribution of ObjectIds, having less nodes full
- // indicates a good chance the total number of children below here
- // is less than the MAX_SIZE split point. Get a more accurate count.
-
- MutableObjectId id = new MutableObjectId();
- id.fromObjectId(noteOn);
-
- int sz = 0;
- for (int cell = 0; cell < 256; cell++) {
- NoteBucket b = table[cell];
- if (b == null)
- continue;
-
- id.setByte(prefixLen >> 1, cell);
- sz += b.estimateSize(id, or);
- if (LeafBucket.MAX_SIZE < sz)
- break;
- }
- return sz;
- }
-
- @Override
- InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData,
- ObjectReader or) throws IOException {
- int cell = cell(noteOn);
- NoteBucket b = table[cell];
-
- if (b == null) {
- if (noteData == null)
- return this;
-
- LeafBucket n = new LeafBucket(prefixLen + 2);
- table[cell] = n.set(noteOn, noteData, or);
- cnt++;
- return this;
-
- } else {
- NoteBucket n = b.set(noteOn, noteData, or);
- if (n == null) {
- table[cell] = null;
- cnt--;
-
- if (cnt == 0)
- return null;
-
- return contractIfTooSmall(noteOn, or);
-
- } else if (n != b) {
- table[cell] = n;
- }
- return this;
- }
- }
-
- InMemoryNoteBucket contractIfTooSmall(AnyObjectId noteOn, ObjectReader or)
- throws IOException {
- if (estimateSize(noteOn, or) < LeafBucket.MAX_SIZE) {
- // We are small enough to just contract to a single leaf.
- InMemoryNoteBucket r = new LeafBucket(prefixLen);
- for (Iterator<Note> i = iterator(noteOn, or); i.hasNext();)
- r = r.append(i.next());
- r.nonNotes = nonNotes;
- return r;
- }
-
- return this;
- }
-
- private static final byte[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
- '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
-
- @Override
- ObjectId writeTree(ObjectInserter inserter) throws IOException {
- return inserter.insert(build(true, inserter));
- }
-
- ObjectId getTreeId() {
- try {
- return new ObjectInserter.Formatter().idFor(build(false, null));
- } catch (IOException e) {
- // should never happen as we are not inserting
- throw new RuntimeException(e);
- }
- }
-
- private TreeFormatter build(boolean insert, ObjectInserter inserter)
- throws IOException {
- byte[] nameBuf = new byte[2];
- TreeFormatter fmt = new TreeFormatter(treeSize());
- NonNoteEntry e = nonNotes;
-
- for (int cell = 0; cell < 256; cell++) {
- NoteBucket b = table[cell];
- if (b == null)
- continue;
-
- nameBuf[0] = hexchar[cell >>> 4];
- nameBuf[1] = hexchar[cell & 0x0f];
-
- while (e != null && e.pathCompare(nameBuf, 0, 2, TREE) < 0) {
- e.format(fmt);
- e = e.next;
- }
-
- ObjectId id;
- if (insert) {
- id = b.writeTree(inserter);
- } else {
- id = b.getTreeId();
- }
- fmt.append(nameBuf, 0, 2, TREE, id);
- }
-
- for (; e != null; e = e.next)
- e.format(fmt);
- return fmt;
- }
-
- private int treeSize() {
- int sz = cnt * TreeFormatter.entrySize(TREE, 2);
- for (NonNoteEntry e = nonNotes; e != null; e = e.next)
- sz += e.treeEntrySize();
- return sz;
- }
-
- @Override
- InMemoryNoteBucket append(Note note) {
- int cell = cell(note);
- InMemoryNoteBucket b = (InMemoryNoteBucket) table[cell];
-
- if (b == null) {
- LeafBucket n = new LeafBucket(prefixLen + 2);
- table[cell] = n.append(note);
- cnt++;
-
- } else {
- InMemoryNoteBucket n = b.append(note);
- if (n != b)
- table[cell] = n;
- }
- return this;
- }
-
- 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
- Note getNote(AnyObjectId objId, ObjectReader or) throws IOException {
- return load(objId, or).getNote(objId, or);
- }
-
- @Override
- Iterator<Note> iterator(AnyObjectId objId, ObjectReader reader)
- throws IOException {
- return load(objId, reader).iterator(objId, reader);
- }
-
- @Override
- int estimateSize(AnyObjectId objId, ObjectReader or) throws IOException {
- return load(objId, or).estimateSize(objId, or);
- }
-
- @Override
- InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData,
- ObjectReader or) throws IOException {
- return load(noteOn, or).set(noteOn, noteData, or);
- }
-
- @Override
- ObjectId writeTree(ObjectInserter inserter) {
- return treeId;
- }
-
- @Override
- ObjectId getTreeId() {
- return treeId;
- }
-
- private InMemoryNoteBucket load(AnyObjectId prefix, ObjectReader or)
- throws IOException {
- AbbreviatedObjectId p = prefix.abbreviate(prefixLen + 2);
- InMemoryNoteBucket self = NoteParser.parse(p, treeId, or);
- table[cell(prefix)] = self;
- return self;
- }
- }
- }
|