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.

LeafBucket.java 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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.Constants.OBJECT_ID_STRING_LENGTH;
  12. import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
  13. import java.io.IOException;
  14. import java.util.Iterator;
  15. import java.util.NoSuchElementException;
  16. import org.eclipse.jgit.lib.AnyObjectId;
  17. import org.eclipse.jgit.lib.ObjectId;
  18. import org.eclipse.jgit.lib.ObjectInserter;
  19. import org.eclipse.jgit.lib.ObjectInserter.Formatter;
  20. import org.eclipse.jgit.lib.ObjectReader;
  21. import org.eclipse.jgit.lib.TreeFormatter;
  22. /**
  23. * A note tree holding only notes, with no subtrees.
  24. *
  25. * The leaf bucket contains on average less than 256 notes, all of whom share
  26. * the same leading prefix. If a notes branch has less than 256 notes, the top
  27. * level tree of the branch should be a LeafBucket. Once a notes branch has more
  28. * than 256 notes, the root should be a {@link FanoutBucket} and the LeafBucket
  29. * will appear only as a cell of a FanoutBucket.
  30. *
  31. * Entries within the LeafBucket are stored sorted by ObjectId, and lookup is
  32. * performed using binary search. As the entry list should contain fewer than
  33. * 256 elements, the average number of compares to find an element should be
  34. * less than 8 due to the O(log N) lookup behavior.
  35. *
  36. * A LeafBucket must be parsed from a tree object by {@link NoteParser}.
  37. */
  38. class LeafBucket extends InMemoryNoteBucket {
  39. static final int MAX_SIZE = 256;
  40. /** All note blobs in this bucket, sorted sequentially. */
  41. private Note[] notes;
  42. /** Number of items in {@link #notes}. */
  43. private int cnt;
  44. LeafBucket(int prefixLen) {
  45. super(prefixLen);
  46. notes = new Note[4];
  47. }
  48. private int search(AnyObjectId objId) {
  49. int low = 0;
  50. int high = cnt;
  51. while (low < high) {
  52. int mid = (low + high) >>> 1;
  53. int cmp = objId.compareTo(notes[mid]);
  54. if (cmp < 0)
  55. high = mid;
  56. else if (cmp == 0)
  57. return mid;
  58. else
  59. low = mid + 1;
  60. }
  61. return -(low + 1);
  62. }
  63. @Override
  64. Note getNote(AnyObjectId objId, ObjectReader or) {
  65. int idx = search(objId);
  66. return 0 <= idx ? notes[idx] : null;
  67. }
  68. Note get(int index) {
  69. return notes[index];
  70. }
  71. int size() {
  72. return cnt;
  73. }
  74. @Override
  75. Iterator<Note> iterator(AnyObjectId objId, ObjectReader reader) {
  76. return new Iterator<Note>() {
  77. private int idx;
  78. @Override
  79. public boolean hasNext() {
  80. return idx < cnt;
  81. }
  82. @Override
  83. public Note next() {
  84. if (hasNext()) {
  85. return notes[idx++];
  86. }
  87. throw new NoSuchElementException();
  88. }
  89. @Override
  90. public void remove() {
  91. throw new UnsupportedOperationException();
  92. }
  93. };
  94. }
  95. @Override
  96. int estimateSize(AnyObjectId noteOn, ObjectReader or) throws IOException {
  97. return cnt;
  98. }
  99. @Override
  100. InMemoryNoteBucket set(AnyObjectId noteOn, AnyObjectId noteData,
  101. ObjectReader or) throws IOException {
  102. int p = search(noteOn);
  103. if (0 <= p) {
  104. if (noteData != null) {
  105. notes[p].setData(noteData.copy());
  106. return this;
  107. }
  108. System.arraycopy(notes, p + 1, notes, p, cnt - p - 1);
  109. cnt--;
  110. return 0 < cnt ? this : null;
  111. } else if (noteData != null) {
  112. if (shouldSplit()) {
  113. return split().set(noteOn, noteData, or);
  114. }
  115. growIfFull();
  116. p = -(p + 1);
  117. if (p < cnt) {
  118. System.arraycopy(notes, p, notes, p + 1, cnt - p);
  119. }
  120. notes[p] = new Note(noteOn, noteData.copy());
  121. cnt++;
  122. return this;
  123. } else {
  124. return this;
  125. }
  126. }
  127. @Override
  128. ObjectId writeTree(ObjectInserter inserter) throws IOException {
  129. return inserter.insert(build());
  130. }
  131. @Override
  132. ObjectId getTreeId() {
  133. try (Formatter f = new ObjectInserter.Formatter()) {
  134. return f.idFor(build());
  135. }
  136. }
  137. private TreeFormatter build() {
  138. byte[] nameBuf = new byte[OBJECT_ID_STRING_LENGTH];
  139. int nameLen = OBJECT_ID_STRING_LENGTH - prefixLen;
  140. TreeFormatter fmt = new TreeFormatter(treeSize(nameLen));
  141. NonNoteEntry e = nonNotes;
  142. for (int i = 0; i < cnt; i++) {
  143. Note n = notes[i];
  144. n.copyTo(nameBuf, 0);
  145. while (e != null
  146. && e.pathCompare(nameBuf, prefixLen, nameLen, REGULAR_FILE) < 0) {
  147. e.format(fmt);
  148. e = e.next;
  149. }
  150. fmt.append(nameBuf, prefixLen, nameLen, REGULAR_FILE, n.getData());
  151. }
  152. for (; e != null; e = e.next)
  153. e.format(fmt);
  154. return fmt;
  155. }
  156. private int treeSize(int nameLen) {
  157. int sz = cnt * TreeFormatter.entrySize(REGULAR_FILE, nameLen);
  158. for (NonNoteEntry e = nonNotes; e != null; e = e.next)
  159. sz += e.treeEntrySize();
  160. return sz;
  161. }
  162. void parseOneEntry(AnyObjectId noteOn, AnyObjectId noteData) {
  163. growIfFull();
  164. notes[cnt++] = new Note(noteOn, noteData.copy());
  165. }
  166. @Override
  167. InMemoryNoteBucket append(Note note) {
  168. if (shouldSplit()) {
  169. return split().append(note);
  170. }
  171. growIfFull();
  172. notes[cnt++] = note;
  173. return this;
  174. }
  175. private void growIfFull() {
  176. if (notes.length == cnt) {
  177. Note[] n = new Note[notes.length * 2];
  178. System.arraycopy(notes, 0, n, 0, cnt);
  179. notes = n;
  180. }
  181. }
  182. private boolean shouldSplit() {
  183. return MAX_SIZE <= cnt && prefixLen + 2 < OBJECT_ID_STRING_LENGTH;
  184. }
  185. FanoutBucket split() {
  186. FanoutBucket n = new FanoutBucket(prefixLen);
  187. for (int i = 0; i < cnt; i++)
  188. n.append(notes[i]);
  189. n.nonNotes = nonNotes;
  190. return n;
  191. }
  192. }