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 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /*
  2. * Copyright (C) 2010, Google Inc. and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.notes;
  11. import static org.eclipse.jgit.lib.FileMode.TREE;
  12. import java.io.IOException;
  13. import java.util.Iterator;
  14. import java.util.NoSuchElementException;
  15. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  16. import org.eclipse.jgit.lib.AnyObjectId;
  17. import org.eclipse.jgit.lib.MutableObjectId;
  18. import org.eclipse.jgit.lib.ObjectId;
  19. import org.eclipse.jgit.lib.ObjectInserter;
  20. import org.eclipse.jgit.lib.ObjectReader;
  21. import org.eclipse.jgit.lib.TreeFormatter;
  22. /**
  23. * A note tree holding only note subtrees, each named using a 2 digit hex name.
  24. *
  25. * The fanout buckets/trees contain on average 256 subtrees, naming the subtrees
  26. * by a slice of the ObjectId contained within them, from "00" through "ff".
  27. *
  28. * Each fanout bucket has a {@link #prefixLen} that defines how many digits it
  29. * skips in an ObjectId before it gets to the digits matching {@link #table}.
  30. *
  31. * The root tree has {@code prefixLen == 0}, and thus does not skip any digits.
  32. * For ObjectId "c0ffee...", the note (if it exists) will be stored within the
  33. * bucket {@code table[0xc0]}.
  34. *
  35. * The first level tree has {@code prefixLen == 2}, and thus skips the first two
  36. * digits. For the same example "c0ffee..." object, its note would be found
  37. * within the {@code table[0xff]} bucket (as first 2 digits "c0" are skipped).
  38. *
  39. * Each subtree is loaded on-demand, reducing startup latency for reads that
  40. * only need to examine a few objects. However, due to the rather uniform
  41. * distribution of the SHA-1 hash that is used for ObjectIds, accessing 256
  42. * objects is very likely to load all of the subtrees into memory.
  43. *
  44. * A FanoutBucket must be parsed from a tree object by {@link NoteParser}.
  45. */
  46. class FanoutBucket extends InMemoryNoteBucket {
  47. /**
  48. * Fan-out table similar to the PackIndex structure.
  49. *
  50. * Notes for an object are stored within the sub-bucket that is held here as
  51. * {@code table[ objectId.getByte( prefixLen / 2 ) ]}. If the slot is null
  52. * there are no notes with that prefix.
  53. */
  54. private final NoteBucket[] table;
  55. /** Number of non-null slots in {@link #table}. */
  56. private int cnt;
  57. FanoutBucket(int prefixLen) {
  58. super(prefixLen);
  59. table = new NoteBucket[256];
  60. }
  61. void setBucket(int cell, ObjectId id) {
  62. table[cell] = new LazyNoteBucket(id);
  63. cnt++;
  64. }
  65. void setBucket(int cell, InMemoryNoteBucket bucket) {
  66. table[cell] = bucket;
  67. cnt++;
  68. }
  69. @Override
  70. Note getNote(AnyObjectId objId, ObjectReader or) throws IOException {
  71. NoteBucket b = table[cell(objId)];
  72. return b != null ? b.getNote(objId, or) : null;
  73. }
  74. NoteBucket getBucket(int cell) {
  75. return table[cell];
  76. }
  77. static InMemoryNoteBucket loadIfLazy(NoteBucket b, AnyObjectId prefix,
  78. ObjectReader or) throws IOException {
  79. if (b == null)
  80. return null;
  81. if (b instanceof InMemoryNoteBucket)
  82. return (InMemoryNoteBucket) b;
  83. return ((LazyNoteBucket) b).load(prefix, or);
  84. }
  85. @Override
  86. Iterator<Note> iterator(AnyObjectId objId, final ObjectReader reader)
  87. throws IOException {
  88. final MutableObjectId id = new MutableObjectId();
  89. id.fromObjectId(objId);
  90. return new Iterator<Note>() {
  91. private int cell;
  92. private Iterator<Note> itr;
  93. @Override
  94. public boolean hasNext() {
  95. if (itr != null && itr.hasNext())
  96. return true;
  97. for (; cell < table.length; cell++) {
  98. NoteBucket b = table[cell];
  99. if (b == null)
  100. continue;
  101. try {
  102. id.setByte(prefixLen >> 1, cell);
  103. itr = b.iterator(id, reader);
  104. } catch (IOException err) {
  105. throw new RuntimeException(err);
  106. }
  107. if (itr.hasNext()) {
  108. cell++;
  109. return true;
  110. }
  111. }
  112. return false;
  113. }
  114. @Override
  115. public Note next() {
  116. if (hasNext()) {
  117. return itr.next();
  118. }
  119. throw new NoSuchElementException();
  120. }
  121. @Override
  122. public void remove() {
  123. throw new UnsupportedOperationException();
  124. }
  125. };
  126. }
  127. @Override
  128. int estimateSize(AnyObjectId noteOn, ObjectReader or) throws IOException {
  129. // If most of this fan-out is full, estimate it should still be split.
  130. if (LeafBucket.MAX_SIZE * 3 / 4 <= cnt)
  131. return 1 + LeafBucket.MAX_SIZE;
  132. // Due to the uniform distribution of ObjectIds, having less nodes full
  133. // indicates a good chance the total number of children below here
  134. // is less than the MAX_SIZE split point. Get a more accurate count.
  135. MutableObjectId id = new MutableObjectId();
  136. id.fromObjectId(noteOn);
  137. int sz = 0;
  138. for (int cell = 0; cell < 256; cell++) {
  139. NoteBucket b = table[cell];
  140. if (b == null)
  141. continue;
  142. id.setByte(prefixLen >> 1, cell);
  143. sz += b.estimateSize(id, or);
  144. if (LeafBucket.MAX_SIZE < sz)
  145. break;
  146. }
  147. return sz;
  148. }
  149. @Override
  150. InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData,
  151. ObjectReader or) throws IOException {
  152. int cell = cell(noteOn);
  153. NoteBucket b = table[cell];
  154. if (b == null) {
  155. if (noteData == null) {
  156. return this;
  157. }
  158. LeafBucket n = new LeafBucket(prefixLen + 2);
  159. table[cell] = n.set(noteOn, noteData, or);
  160. cnt++;
  161. return this;
  162. }
  163. NoteBucket n = b.set(noteOn, noteData, or);
  164. if (n == null) {
  165. table[cell] = null;
  166. cnt--;
  167. if (cnt == 0) {
  168. return null;
  169. }
  170. return contractIfTooSmall(noteOn, or);
  171. } else if (n != b) {
  172. table[cell] = n;
  173. }
  174. return this;
  175. }
  176. InMemoryNoteBucket contractIfTooSmall(AnyObjectId noteOn, ObjectReader or)
  177. throws IOException {
  178. if (estimateSize(noteOn, or) < LeafBucket.MAX_SIZE) {
  179. // We are small enough to just contract to a single leaf.
  180. InMemoryNoteBucket r = new LeafBucket(prefixLen);
  181. for (Iterator<Note> i = iterator(noteOn, or); i.hasNext();)
  182. r = r.append(i.next());
  183. r.nonNotes = nonNotes;
  184. return r;
  185. }
  186. return this;
  187. }
  188. private static final byte[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
  189. '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
  190. @Override
  191. ObjectId writeTree(ObjectInserter inserter) throws IOException {
  192. return inserter.insert(build(true, inserter));
  193. }
  194. @Override
  195. ObjectId getTreeId() {
  196. try (ObjectInserter.Formatter f = new ObjectInserter.Formatter()) {
  197. return f.idFor(build(false, null));
  198. } catch (IOException e) {
  199. // should never happen as we are not inserting
  200. throw new RuntimeException(e);
  201. }
  202. }
  203. private TreeFormatter build(boolean insert, ObjectInserter inserter)
  204. throws IOException {
  205. byte[] nameBuf = new byte[2];
  206. TreeFormatter fmt = new TreeFormatter(treeSize());
  207. NonNoteEntry e = nonNotes;
  208. for (int cell = 0; cell < 256; cell++) {
  209. NoteBucket b = table[cell];
  210. if (b == null)
  211. continue;
  212. nameBuf[0] = hexchar[cell >>> 4];
  213. nameBuf[1] = hexchar[cell & 0x0f];
  214. while (e != null && e.pathCompare(nameBuf, 0, 2, TREE) < 0) {
  215. e.format(fmt);
  216. e = e.next;
  217. }
  218. ObjectId id;
  219. if (insert) {
  220. id = b.writeTree(inserter);
  221. } else {
  222. id = b.getTreeId();
  223. }
  224. fmt.append(nameBuf, 0, 2, TREE, id);
  225. }
  226. for (; e != null; e = e.next)
  227. e.format(fmt);
  228. return fmt;
  229. }
  230. private int treeSize() {
  231. int sz = cnt * TreeFormatter.entrySize(TREE, 2);
  232. for (NonNoteEntry e = nonNotes; e != null; e = e.next)
  233. sz += e.treeEntrySize();
  234. return sz;
  235. }
  236. @Override
  237. InMemoryNoteBucket append(Note note) {
  238. int cell = cell(note);
  239. InMemoryNoteBucket b = (InMemoryNoteBucket) table[cell];
  240. if (b == null) {
  241. LeafBucket n = new LeafBucket(prefixLen + 2);
  242. table[cell] = n.append(note);
  243. cnt++;
  244. } else {
  245. InMemoryNoteBucket n = b.append(note);
  246. if (n != b)
  247. table[cell] = n;
  248. }
  249. return this;
  250. }
  251. private int cell(AnyObjectId id) {
  252. return id.getByte(prefixLen >> 1);
  253. }
  254. private class LazyNoteBucket extends NoteBucket {
  255. private final ObjectId treeId;
  256. LazyNoteBucket(ObjectId treeId) {
  257. this.treeId = treeId;
  258. }
  259. @Override
  260. Note getNote(AnyObjectId objId, ObjectReader or) throws IOException {
  261. return load(objId, or).getNote(objId, or);
  262. }
  263. @Override
  264. Iterator<Note> iterator(AnyObjectId objId, ObjectReader reader)
  265. throws IOException {
  266. return load(objId, reader).iterator(objId, reader);
  267. }
  268. @Override
  269. int estimateSize(AnyObjectId objId, ObjectReader or) throws IOException {
  270. return load(objId, or).estimateSize(objId, or);
  271. }
  272. @Override
  273. InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData,
  274. ObjectReader or) throws IOException {
  275. return load(noteOn, or).set(noteOn, noteData, or);
  276. }
  277. @Override
  278. ObjectId writeTree(ObjectInserter inserter) {
  279. return treeId;
  280. }
  281. @Override
  282. ObjectId getTreeId() {
  283. return treeId;
  284. }
  285. private InMemoryNoteBucket load(AnyObjectId prefix, ObjectReader or)
  286. throws IOException {
  287. AbbreviatedObjectId p = prefix.abbreviate(prefixLen + 2);
  288. InMemoryNoteBucket self = NoteParser.parse(p, treeId, or);
  289. table[cell(prefix)] = self;
  290. return self;
  291. }
  292. }
  293. }