You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FanoutBucket.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. /*
  2. * Copyright (C) 2010, Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.notes;
  44. import static org.eclipse.jgit.lib.FileMode.TREE;
  45. import java.io.IOException;
  46. import java.util.Iterator;
  47. import java.util.NoSuchElementException;
  48. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  49. import org.eclipse.jgit.lib.AnyObjectId;
  50. import org.eclipse.jgit.lib.MutableObjectId;
  51. import org.eclipse.jgit.lib.ObjectId;
  52. import org.eclipse.jgit.lib.ObjectInserter;
  53. import org.eclipse.jgit.lib.ObjectReader;
  54. import org.eclipse.jgit.lib.TreeFormatter;
  55. /**
  56. * A note tree holding only note subtrees, each named using a 2 digit hex name.
  57. *
  58. * The fanout buckets/trees contain on average 256 subtrees, naming the subtrees
  59. * by a slice of the ObjectId contained within them, from "00" through "ff".
  60. *
  61. * Each fanout bucket has a {@link #prefixLen} that defines how many digits it
  62. * skips in an ObjectId before it gets to the digits matching {@link #table}.
  63. *
  64. * The root tree has {@code prefixLen == 0}, and thus does not skip any digits.
  65. * For ObjectId "c0ffee...", the note (if it exists) will be stored within the
  66. * bucket {@code table[0xc0]}.
  67. *
  68. * The first level tree has {@code prefixLen == 2}, and thus skips the first two
  69. * digits. For the same example "c0ffee..." object, its note would be found
  70. * within the {@code table[0xff]} bucket (as first 2 digits "c0" are skipped).
  71. *
  72. * Each subtree is loaded on-demand, reducing startup latency for reads that
  73. * only need to examine a few objects. However, due to the rather uniform
  74. * distribution of the SHA-1 hash that is used for ObjectIds, accessing 256
  75. * objects is very likely to load all of the subtrees into memory.
  76. *
  77. * A FanoutBucket must be parsed from a tree object by {@link NoteParser}.
  78. */
  79. class FanoutBucket extends InMemoryNoteBucket {
  80. /**
  81. * Fan-out table similar to the PackIndex structure.
  82. *
  83. * Notes for an object are stored within the sub-bucket that is held here as
  84. * {@code table[ objectId.getByte( prefixLen / 2 ) ]}. If the slot is null
  85. * there are no notes with that prefix.
  86. */
  87. private final NoteBucket[] table;
  88. /** Number of non-null slots in {@link #table}. */
  89. private int cnt;
  90. FanoutBucket(int prefixLen) {
  91. super(prefixLen);
  92. table = new NoteBucket[256];
  93. }
  94. void setBucket(int cell, ObjectId id) {
  95. table[cell] = new LazyNoteBucket(id);
  96. cnt++;
  97. }
  98. void setBucket(int cell, InMemoryNoteBucket bucket) {
  99. table[cell] = bucket;
  100. cnt++;
  101. }
  102. @Override
  103. Note getNote(AnyObjectId objId, ObjectReader or) throws IOException {
  104. NoteBucket b = table[cell(objId)];
  105. return b != null ? b.getNote(objId, or) : null;
  106. }
  107. NoteBucket getBucket(int cell) {
  108. return table[cell];
  109. }
  110. static InMemoryNoteBucket loadIfLazy(NoteBucket b, AnyObjectId prefix,
  111. ObjectReader or) throws IOException {
  112. if (b == null)
  113. return null;
  114. if (b instanceof InMemoryNoteBucket)
  115. return (InMemoryNoteBucket) b;
  116. return ((LazyNoteBucket) b).load(prefix, or);
  117. }
  118. @Override
  119. Iterator<Note> iterator(AnyObjectId objId, final ObjectReader reader)
  120. throws IOException {
  121. final MutableObjectId id = new MutableObjectId();
  122. id.fromObjectId(objId);
  123. return new Iterator<Note>() {
  124. private int cell;
  125. private Iterator<Note> itr;
  126. @Override
  127. public boolean hasNext() {
  128. if (itr != null && itr.hasNext())
  129. return true;
  130. for (; cell < table.length; cell++) {
  131. NoteBucket b = table[cell];
  132. if (b == null)
  133. continue;
  134. try {
  135. id.setByte(prefixLen >> 1, cell);
  136. itr = b.iterator(id, reader);
  137. } catch (IOException err) {
  138. throw new RuntimeException(err);
  139. }
  140. if (itr.hasNext()) {
  141. cell++;
  142. return true;
  143. }
  144. }
  145. return false;
  146. }
  147. @Override
  148. public Note next() {
  149. if (hasNext()) {
  150. return itr.next();
  151. }
  152. throw new NoSuchElementException();
  153. }
  154. @Override
  155. public void remove() {
  156. throw new UnsupportedOperationException();
  157. }
  158. };
  159. }
  160. @Override
  161. int estimateSize(AnyObjectId noteOn, ObjectReader or) throws IOException {
  162. // If most of this fan-out is full, estimate it should still be split.
  163. if (LeafBucket.MAX_SIZE * 3 / 4 <= cnt)
  164. return 1 + LeafBucket.MAX_SIZE;
  165. // Due to the uniform distribution of ObjectIds, having less nodes full
  166. // indicates a good chance the total number of children below here
  167. // is less than the MAX_SIZE split point. Get a more accurate count.
  168. MutableObjectId id = new MutableObjectId();
  169. id.fromObjectId(noteOn);
  170. int sz = 0;
  171. for (int cell = 0; cell < 256; cell++) {
  172. NoteBucket b = table[cell];
  173. if (b == null)
  174. continue;
  175. id.setByte(prefixLen >> 1, cell);
  176. sz += b.estimateSize(id, or);
  177. if (LeafBucket.MAX_SIZE < sz)
  178. break;
  179. }
  180. return sz;
  181. }
  182. @Override
  183. InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData,
  184. ObjectReader or) throws IOException {
  185. int cell = cell(noteOn);
  186. NoteBucket b = table[cell];
  187. if (b == null) {
  188. if (noteData == null) {
  189. return this;
  190. }
  191. LeafBucket n = new LeafBucket(prefixLen + 2);
  192. table[cell] = n.set(noteOn, noteData, or);
  193. cnt++;
  194. return this;
  195. }
  196. NoteBucket n = b.set(noteOn, noteData, or);
  197. if (n == null) {
  198. table[cell] = null;
  199. cnt--;
  200. if (cnt == 0) {
  201. return null;
  202. }
  203. return contractIfTooSmall(noteOn, or);
  204. } else if (n != b) {
  205. table[cell] = n;
  206. }
  207. return this;
  208. }
  209. InMemoryNoteBucket contractIfTooSmall(AnyObjectId noteOn, ObjectReader or)
  210. throws IOException {
  211. if (estimateSize(noteOn, or) < LeafBucket.MAX_SIZE) {
  212. // We are small enough to just contract to a single leaf.
  213. InMemoryNoteBucket r = new LeafBucket(prefixLen);
  214. for (Iterator<Note> i = iterator(noteOn, or); i.hasNext();)
  215. r = r.append(i.next());
  216. r.nonNotes = nonNotes;
  217. return r;
  218. }
  219. return this;
  220. }
  221. private static final byte[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
  222. '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
  223. @Override
  224. ObjectId writeTree(ObjectInserter inserter) throws IOException {
  225. return inserter.insert(build(true, inserter));
  226. }
  227. @Override
  228. ObjectId getTreeId() {
  229. try (ObjectInserter.Formatter f = new ObjectInserter.Formatter()) {
  230. return f.idFor(build(false, null));
  231. } catch (IOException e) {
  232. // should never happen as we are not inserting
  233. throw new RuntimeException(e);
  234. }
  235. }
  236. private TreeFormatter build(boolean insert, ObjectInserter inserter)
  237. throws IOException {
  238. byte[] nameBuf = new byte[2];
  239. TreeFormatter fmt = new TreeFormatter(treeSize());
  240. NonNoteEntry e = nonNotes;
  241. for (int cell = 0; cell < 256; cell++) {
  242. NoteBucket b = table[cell];
  243. if (b == null)
  244. continue;
  245. nameBuf[0] = hexchar[cell >>> 4];
  246. nameBuf[1] = hexchar[cell & 0x0f];
  247. while (e != null && e.pathCompare(nameBuf, 0, 2, TREE) < 0) {
  248. e.format(fmt);
  249. e = e.next;
  250. }
  251. ObjectId id;
  252. if (insert) {
  253. id = b.writeTree(inserter);
  254. } else {
  255. id = b.getTreeId();
  256. }
  257. fmt.append(nameBuf, 0, 2, TREE, id);
  258. }
  259. for (; e != null; e = e.next)
  260. e.format(fmt);
  261. return fmt;
  262. }
  263. private int treeSize() {
  264. int sz = cnt * TreeFormatter.entrySize(TREE, 2);
  265. for (NonNoteEntry e = nonNotes; e != null; e = e.next)
  266. sz += e.treeEntrySize();
  267. return sz;
  268. }
  269. @Override
  270. InMemoryNoteBucket append(Note note) {
  271. int cell = cell(note);
  272. InMemoryNoteBucket b = (InMemoryNoteBucket) table[cell];
  273. if (b == null) {
  274. LeafBucket n = new LeafBucket(prefixLen + 2);
  275. table[cell] = n.append(note);
  276. cnt++;
  277. } else {
  278. InMemoryNoteBucket n = b.append(note);
  279. if (n != b)
  280. table[cell] = n;
  281. }
  282. return this;
  283. }
  284. private int cell(AnyObjectId id) {
  285. return id.getByte(prefixLen >> 1);
  286. }
  287. private class LazyNoteBucket extends NoteBucket {
  288. private final ObjectId treeId;
  289. LazyNoteBucket(ObjectId treeId) {
  290. this.treeId = treeId;
  291. }
  292. @Override
  293. Note getNote(AnyObjectId objId, ObjectReader or) throws IOException {
  294. return load(objId, or).getNote(objId, or);
  295. }
  296. @Override
  297. Iterator<Note> iterator(AnyObjectId objId, ObjectReader reader)
  298. throws IOException {
  299. return load(objId, reader).iterator(objId, reader);
  300. }
  301. @Override
  302. int estimateSize(AnyObjectId objId, ObjectReader or) throws IOException {
  303. return load(objId, or).estimateSize(objId, or);
  304. }
  305. @Override
  306. InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData,
  307. ObjectReader or) throws IOException {
  308. return load(noteOn, or).set(noteOn, noteData, or);
  309. }
  310. @Override
  311. ObjectId writeTree(ObjectInserter inserter) {
  312. return treeId;
  313. }
  314. @Override
  315. ObjectId getTreeId() {
  316. return treeId;
  317. }
  318. private InMemoryNoteBucket load(AnyObjectId prefix, ObjectReader or)
  319. throws IOException {
  320. AbbreviatedObjectId p = prefix.abbreviate(prefixLen + 2);
  321. InMemoryNoteBucket self = NoteParser.parse(p, treeId, or);
  322. table[cell(prefix)] = self;
  323. return self;
  324. }
  325. }
  326. }