LeafBucket.java

  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<>() {
  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. }