ReftableWriter.java

  1. /*
  2.  * Copyright (C) 2017, 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.internal.storage.reftable;

  11. import static java.lang.Math.log;
  12. import static java.nio.charset.StandardCharsets.UTF_8;
  13. import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.padBetweenBlocks;
  14. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN;
  15. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
  16. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_MAGIC;
  17. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
  18. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
  19. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_BLOCK_SIZE;
  20. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS;
  21. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
  22. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
  23. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1;
  24. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;

  25. import java.io.IOException;
  26. import java.io.OutputStream;
  27. import java.text.MessageFormat;
  28. import java.util.ArrayList;
  29. import java.util.Collection;
  30. import java.util.Collections;
  31. import java.util.HashSet;
  32. import java.util.Iterator;
  33. import java.util.List;
  34. import java.util.Set;
  35. import java.util.zip.CRC32;

  36. import org.eclipse.jgit.annotations.Nullable;
  37. import org.eclipse.jgit.internal.JGitText;
  38. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.DeleteLogEntry;
  39. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.Entry;
  40. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.IndexEntry;
  41. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry;
  42. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.ObjEntry;
  43. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.RefEntry;
  44. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  45. import org.eclipse.jgit.lib.AnyObjectId;
  46. import org.eclipse.jgit.lib.ObjectId;
  47. import org.eclipse.jgit.lib.ObjectIdOwnerMap;
  48. import org.eclipse.jgit.lib.ObjectIdSubclassMap;
  49. import org.eclipse.jgit.lib.PersonIdent;
  50. import org.eclipse.jgit.lib.Ref;
  51. import org.eclipse.jgit.util.LongList;
  52. import org.eclipse.jgit.util.NB;

  53. /**
  54.  * Writes a reftable formatted file.
  55.  * <p>
  56.  * A reftable can be written in a streaming fashion, provided the caller sorts
  57.  * all references. A
  58.  * {@link org.eclipse.jgit.internal.storage.reftable.ReftableWriter} is
  59.  * single-use, and not thread-safe.
  60.  */
  61. public class ReftableWriter {
  62.     private ReftableConfig config;
  63.     private int refBlockSize;
  64.     private int logBlockSize;
  65.     private int restartInterval;
  66.     private int maxIndexLevels;
  67.     private boolean alignBlocks;
  68.     private boolean indexObjects;

  69.     private long minUpdateIndex;
  70.     private long maxUpdateIndex;

  71.     private OutputStream outputStream;
  72.     private ReftableOutputStream out;
  73.     private ObjectIdSubclassMap<RefList> obj2ref;

  74.     private BlockWriter.Entry lastRef;
  75.     private BlockWriter.Entry lastLog;
  76.     private BlockWriter cur;
  77.     private Section refs;
  78.     private Section objs;
  79.     private Section logs;
  80.     private int objIdLen;
  81.     private Stats stats;

  82.     /**
  83.      * Initialize a writer with a default configuration.
  84.      *
  85.      * @param os
  86.      *            output stream.
  87.      */
  88.     public ReftableWriter(OutputStream os) {
  89.         this(new ReftableConfig(), os);
  90.         lastRef = null;
  91.         lastLog = null;
  92.     }

  93.     /**
  94.      * Initialize a writer with a configuration.
  95.      *
  96.      * @param cfg
  97.      *            configuration for the writer
  98.      * @param os
  99.      *            output stream.
  100.      */
  101.     public ReftableWriter(ReftableConfig cfg, OutputStream os) {
  102.         config = cfg;
  103.         outputStream = os;
  104.     }

  105.     /**
  106.      * Set configuration for the writer.
  107.      *
  108.      * @param cfg
  109.      *            configuration for the writer.
  110.      * @return {@code this}
  111.      */
  112.     public ReftableWriter setConfig(ReftableConfig cfg) {
  113.         this.config = cfg != null ? cfg : new ReftableConfig();
  114.         return this;
  115.     }

  116.     /**
  117.      * Set the minimum update index for log entries that appear in this
  118.      * reftable.
  119.      *
  120.      * @param min
  121.      *            the minimum update index for log entries that appear in this
  122.      *            reftable. This should be 1 higher than the prior reftable's
  123.      *            {@code maxUpdateIndex} if this table will be used in a stack.
  124.      * @return {@code this}
  125.      */
  126.     public ReftableWriter setMinUpdateIndex(long min) {
  127.         minUpdateIndex = min;
  128.         return this;
  129.     }

  130.     /**
  131.      * Set the maximum update index for log entries that appear in this
  132.      * reftable.
  133.      *
  134.      * @param max
  135.      *            the maximum update index for log entries that appear in this
  136.      *            reftable. This should be at least 1 higher than the prior
  137.      *            reftable's {@code maxUpdateIndex} if this table will be used
  138.      *            in a stack.
  139.      * @return {@code this}
  140.      */
  141.     public ReftableWriter setMaxUpdateIndex(long max) {
  142.         maxUpdateIndex = max;
  143.         return this;
  144.     }

  145.     /**
  146.      * Begin writing the reftable. Should be called only once. Call this
  147.      * if a stream was passed to the constructor.
  148.      *
  149.      * @return {@code this}
  150.      */
  151.     public ReftableWriter begin() {
  152.         if (out != null) {
  153.             throw new IllegalStateException("begin() called twice.");//$NON-NLS-1$
  154.         }

  155.         refBlockSize = config.getRefBlockSize();
  156.         logBlockSize = config.getLogBlockSize();
  157.         restartInterval = config.getRestartInterval();
  158.         maxIndexLevels = config.getMaxIndexLevels();
  159.         alignBlocks = config.isAlignBlocks();
  160.         indexObjects = config.isIndexObjects();

  161.         if (refBlockSize <= 0) {
  162.             refBlockSize = 4 << 10;
  163.         } else if (refBlockSize > MAX_BLOCK_SIZE) {
  164.             throw new IllegalArgumentException();
  165.         }
  166.         if (logBlockSize <= 0) {
  167.             logBlockSize = 2 * refBlockSize;
  168.         }
  169.         if (restartInterval <= 0) {
  170.             restartInterval = refBlockSize < (60 << 10) ? 16 : 64;
  171.         }

  172.         out = new ReftableOutputStream(outputStream, refBlockSize, alignBlocks);
  173.         refs = new Section(REF_BLOCK_TYPE);
  174.         if (indexObjects) {
  175.             obj2ref = new ObjectIdSubclassMap<>();
  176.         }
  177.         writeFileHeader();
  178.         return this;
  179.     }

  180.     /**
  181.      * Sort a collection of references and write them to the reftable.
  182.      * The input refs may not have duplicate names.
  183.      *
  184.      * @param refsToPack
  185.      *            references to sort and write.
  186.      * @return {@code this}
  187.      * @throws java.io.IOException
  188.      *             if reftable cannot be written.
  189.      */
  190.     public ReftableWriter sortAndWriteRefs(Collection<Ref> refsToPack)
  191.             throws IOException {
  192.         Iterator<RefEntry> itr = refsToPack.stream()
  193.                 .map(r -> new RefEntry(r, maxUpdateIndex - minUpdateIndex))
  194.                 .sorted(Entry::compare)
  195.                 .iterator();
  196.         RefEntry last = null;
  197.         while (itr.hasNext()) {
  198.             RefEntry entry = itr.next();
  199.             if (last != null && Entry.compare(last, entry) == 0) {
  200.                 throwIllegalEntry(last, entry);
  201.             }

  202.             long blockPos = refs.write(entry);
  203.             indexRef(entry.ref, blockPos);
  204.             last = entry;
  205.         }
  206.         return this;
  207.     }

  208.     /**
  209.      * Write one reference to the reftable.
  210.      * <p>
  211.      * References must be passed in sorted order.
  212.      *
  213.      * @param ref
  214.      *            the reference to store.
  215.      * @throws java.io.IOException
  216.      *             if reftable cannot be written.
  217.      */
  218.     public void writeRef(Ref ref) throws IOException {
  219.         writeRef(ref, maxUpdateIndex);
  220.     }

  221.     /**
  222.      * Write one reference to the reftable.
  223.      * <p>
  224.      * References must be passed in sorted order.
  225.      *
  226.      * @param ref
  227.      *            the reference to store.
  228.      * @param updateIndex
  229.      *            the updateIndex that modified this reference. Must be
  230.      *            {@code >= minUpdateIndex} for this file.
  231.      * @throws java.io.IOException
  232.      *             if reftable cannot be written.
  233.      */
  234.     public void writeRef(Ref ref, long updateIndex) throws IOException {
  235.         if (updateIndex < minUpdateIndex) {
  236.             throw new IllegalArgumentException();
  237.         }
  238.         long d = updateIndex - minUpdateIndex;
  239.         RefEntry entry = new RefEntry(ref, d);
  240.         if (lastRef != null && Entry.compare(lastRef, entry) >= 0) {
  241.             throwIllegalEntry(lastRef, entry);
  242.         }
  243.         lastRef = entry;

  244.         long blockPos = refs.write(entry);
  245.         indexRef(ref, blockPos);
  246.     }

  247.     private void throwIllegalEntry(Entry last, Entry now) {
  248.         throw new IllegalArgumentException(MessageFormat.format(
  249.                 JGitText.get().reftableRecordsMustIncrease,
  250.                 new String(last.key, UTF_8), new String(now.key, UTF_8)));
  251.     }

  252.     private void indexRef(Ref ref, long blockPos) {
  253.         if (indexObjects && !ref.isSymbolic()) {
  254.             indexId(ref.getObjectId(), blockPos);
  255.             indexId(ref.getPeeledObjectId(), blockPos);
  256.         }
  257.     }

  258.     private void indexId(ObjectId id, long blockPos) {
  259.         if (id != null) {
  260.             RefList l = obj2ref.get(id);
  261.             if (l == null) {
  262.                 l = new RefList(id);
  263.                 obj2ref.add(l);
  264.             }
  265.             l.addBlock(blockPos);
  266.         }
  267.     }

  268.     /**
  269.      * Write one reflog entry to the reftable.
  270.      * <p>
  271.      * Reflog entries must be written in reference name and descending
  272.      * {@code updateIndex} (highest first) order.
  273.      *
  274.      * @param ref
  275.      *            name of the reference.
  276.      * @param updateIndex
  277.      *            identifier of the transaction that created the log record. The
  278.      *            {@code updateIndex} must be unique within the scope of
  279.      *            {@code ref}, and must be within the bounds defined by
  280.      *            {@code minUpdateIndex <= updateIndex <= maxUpdateIndex}.
  281.      * @param who
  282.      *            committer of the reflog entry.
  283.      * @param oldId
  284.      *            prior id; pass {@link org.eclipse.jgit.lib.ObjectId#zeroId()}
  285.      *            for creations.
  286.      * @param newId
  287.      *            new id; pass {@link org.eclipse.jgit.lib.ObjectId#zeroId()}
  288.      *            for deletions.
  289.      * @param message
  290.      *            optional message (may be null).
  291.      * @throws java.io.IOException
  292.      *             if reftable cannot be written.
  293.      */
  294.     public void writeLog(String ref, long updateIndex, PersonIdent who,
  295.             ObjectId oldId, ObjectId newId, @Nullable String message)
  296.                     throws IOException {
  297.         String msg = message != null ? message : ""; //$NON-NLS-1$
  298.         beginLog();
  299.         LogEntry entry = new LogEntry(ref, updateIndex, who, oldId, newId, msg);
  300.         if (lastLog != null && Entry.compare(lastLog, entry) >= 0) {
  301.             throwIllegalEntry(lastLog, entry);
  302.         }
  303.         lastLog = entry;
  304.         logs.write(entry);
  305.     }

  306.     /**
  307.      * Record deletion of one reflog entry in this reftable.
  308.      *
  309.      * <p>
  310.      * The deletion can shadow an entry stored in a lower table in the stack.
  311.      * This is useful for {@code refs/stash} and dropping an entry from its
  312.      * reflog.
  313.      * <p>
  314.      * Deletion must be properly interleaved in sorted updateIndex order with
  315.      * any other logs written by
  316.      * {@link #writeLog(String, long, PersonIdent, ObjectId, ObjectId, String)}.
  317.      *
  318.      * @param ref
  319.      *            the ref to delete (hide) a reflog entry from.
  320.      * @param updateIndex
  321.      *            the update index that must be hidden.
  322.      * @throws java.io.IOException
  323.      *             if reftable cannot be written.
  324.      */
  325.     public void deleteLog(String ref, long updateIndex) throws IOException {
  326.         beginLog();
  327.         logs.write(new DeleteLogEntry(ref, updateIndex));
  328.     }

  329.     private void beginLog() throws IOException {
  330.         if (logs == null) {
  331.             finishRefAndObjSections(); // close prior ref blocks and their index, if present.
  332.             out.flushFileHeader();
  333.             out.setBlockSize(logBlockSize);
  334.             logs = new Section(LOG_BLOCK_TYPE);
  335.         }
  336.     }

  337.     /**
  338.      * Get an estimate of the current size in bytes of the reftable
  339.      *
  340.      * @return an estimate of the current size in bytes of the reftable, if it
  341.      *         was finished right now. Estimate is only accurate if
  342.      *         {@link org.eclipse.jgit.internal.storage.reftable.ReftableConfig#setIndexObjects(boolean)}
  343.      *         is {@code false} and
  344.      *         {@link org.eclipse.jgit.internal.storage.reftable.ReftableConfig#setMaxIndexLevels(int)}
  345.      *         is {@code 1}.
  346.      */
  347.     public long estimateTotalBytes() {
  348.         long bytes = out.size();
  349.         if (bytes == 0) {
  350.             bytes += FILE_HEADER_LEN;
  351.         }
  352.         if (cur != null) {
  353.             long curBlockPos = out.size();
  354.             int sz = cur.currentSize();
  355.             bytes += sz;

  356.             IndexBuilder idx = null;
  357.             if (cur.blockType() == REF_BLOCK_TYPE) {
  358.                 idx = refs.idx;
  359.             } else if (cur.blockType() == LOG_BLOCK_TYPE) {
  360.                 idx = logs.idx;
  361.             }
  362.             if (idx != null && shouldHaveIndex(idx)) {
  363.                 if (idx == refs.idx) {
  364.                     bytes += out.estimatePadBetweenBlocks(sz);
  365.                 }
  366.                 bytes += idx.estimateBytes(curBlockPos);
  367.             }
  368.         }
  369.         bytes += FILE_FOOTER_LEN;
  370.         return bytes;
  371.     }

  372.     /**
  373.      * Finish writing the reftable by writing its trailer.
  374.      *
  375.      * @return {@code this}
  376.      * @throws java.io.IOException
  377.      *             if reftable cannot be written.
  378.      */
  379.     public ReftableWriter finish() throws IOException {
  380.         finishRefAndObjSections();
  381.         finishLogSection();
  382.         writeFileFooter();
  383.         out.finishFile();

  384.         stats = new Stats(this, out);
  385.         out = null;
  386.         obj2ref = null;
  387.         cur = null;
  388.         refs = null;
  389.         objs = null;
  390.         logs = null;
  391.         return this;
  392.     }

  393.     private void finishRefAndObjSections() throws IOException {
  394.         if (cur != null && cur.blockType() == REF_BLOCK_TYPE) {
  395.             refs.finishSectionMaybeWriteIndex();
  396.             if (indexObjects && !obj2ref.isEmpty() && refs.idx.bytes > 0) {
  397.                 writeObjBlocks();
  398.             }
  399.             obj2ref = null;
  400.         }
  401.     }

  402.     private void writeObjBlocks() throws IOException {
  403.         List<RefList> sorted = sortById(obj2ref);
  404.         obj2ref = null;
  405.         objIdLen = shortestUniqueAbbreviation(sorted);

  406.         out.padBetweenBlocksToNextBlock();
  407.         objs = new Section(OBJ_BLOCK_TYPE);
  408.         objs.entryCnt = sorted.size();
  409.         for (RefList l : sorted) {
  410.             objs.write(new ObjEntry(objIdLen, l, l.blockPos));
  411.         }
  412.         objs.finishSectionMaybeWriteIndex();
  413.     }

  414.     private void finishLogSection() throws IOException {
  415.         if (cur != null && cur.blockType() == LOG_BLOCK_TYPE) {
  416.             logs.finishSectionMaybeWriteIndex();
  417.         }
  418.     }

  419.     private boolean shouldHaveIndex(IndexBuilder idx) {
  420.         int threshold;
  421.         if (idx == refs.idx && alignBlocks) {
  422.             threshold = 4;
  423.         } else {
  424.             threshold = 1;
  425.         }
  426.         return idx.entries.size() + (cur != null ? 1 : 0) > threshold;
  427.     }

  428.     private void writeFileHeader() {
  429.         byte[] hdr = new byte[FILE_HEADER_LEN];
  430.         encodeHeader(hdr);
  431.         out.write(hdr, 0, FILE_HEADER_LEN);
  432.     }

  433.     private void encodeHeader(byte[] hdr) {
  434.         System.arraycopy(FILE_HEADER_MAGIC, 0, hdr, 0, 4);
  435.         int bs = alignBlocks ? refBlockSize : 0;
  436.         NB.encodeInt32(hdr, 4, (VERSION_1 << 24) | bs);
  437.         NB.encodeInt64(hdr, 8, minUpdateIndex);
  438.         NB.encodeInt64(hdr, 16, maxUpdateIndex);
  439.     }

  440.     private void writeFileFooter() {
  441.         int ftrLen = FILE_FOOTER_LEN;
  442.         byte[] ftr = new byte[ftrLen];
  443.         encodeHeader(ftr);

  444.         NB.encodeInt64(ftr, 24, indexPosition(refs));
  445.         NB.encodeInt64(ftr, 32, (firstBlockPosition(objs) << 5) | objIdLen);
  446.         NB.encodeInt64(ftr, 40, indexPosition(objs));
  447.         NB.encodeInt64(ftr, 48, firstBlockPosition(logs));
  448.         NB.encodeInt64(ftr, 56, indexPosition(logs));

  449.         CRC32 crc = new CRC32();
  450.         crc.update(ftr, 0, ftrLen - 4);
  451.         NB.encodeInt32(ftr, ftrLen - 4, (int) crc.getValue());

  452.         out.write(ftr, 0, ftrLen);
  453.     }

  454.     private static long firstBlockPosition(@Nullable Section s) {
  455.         return s != null ? s.firstBlockPosition : 0;
  456.     }

  457.     private static long indexPosition(@Nullable Section s) {
  458.         return s != null && s.idx != null ? s.idx.rootPosition : 0;
  459.     }

  460.     /**
  461.      * Get statistics of the last written reftable.
  462.      *
  463.      * @return statistics of the last written reftable.
  464.      */
  465.     public Stats getStats() {
  466.         return stats;
  467.     }

  468.     /** Statistics about a written reftable. */
  469.     public static class Stats {
  470.         private final int refBlockSize;
  471.         private final int logBlockSize;
  472.         private final int restartInterval;

  473.         private final long minUpdateIndex;
  474.         private final long maxUpdateIndex;

  475.         private final long refCnt;
  476.         private final long objCnt;
  477.         private final int objIdLen;
  478.         private final long logCnt;
  479.         private final long refBytes;
  480.         private final long objBytes;
  481.         private final long logBytes;
  482.         private final long paddingUsed;
  483.         private final long totalBytes;

  484.         private final int refIndexSize;
  485.         private final int refIndexLevels;
  486.         private final int objIndexSize;
  487.         private final int objIndexLevels;

  488.         Stats(ReftableWriter w, ReftableOutputStream o) {
  489.             refBlockSize = w.refBlockSize;
  490.             logBlockSize = w.logBlockSize;
  491.             restartInterval = w.restartInterval;

  492.             minUpdateIndex = w.minUpdateIndex;
  493.             maxUpdateIndex = w.maxUpdateIndex;
  494.             paddingUsed = o.paddingUsed();
  495.             totalBytes = o.size();

  496.             refCnt = w.refs.entryCnt;
  497.             refBytes = w.refs.bytes;

  498.             objCnt = w.objs != null ? w.objs.entryCnt : 0;
  499.             objBytes = w.objs != null ? w.objs.bytes : 0;
  500.             objIdLen = w.objIdLen;

  501.             logCnt = w.logs != null ? w.logs.entryCnt : 0;
  502.             logBytes = w.logs != null ? w.logs.bytes : 0;

  503.             IndexBuilder refIdx = w.refs.idx;
  504.             refIndexSize = refIdx.bytes;
  505.             refIndexLevels = refIdx.levels;

  506.             IndexBuilder objIdx = w.objs != null ? w.objs.idx : null;
  507.             objIndexSize = objIdx != null ? objIdx.bytes : 0;
  508.             objIndexLevels = objIdx != null ? objIdx.levels : 0;
  509.         }

  510.         /** @return number of bytes in a ref block. */
  511.         public int refBlockSize() {
  512.             return refBlockSize;
  513.         }

  514.         /** @return number of bytes to compress into a log block. */
  515.         public int logBlockSize() {
  516.             return logBlockSize;
  517.         }

  518.         /** @return number of references between binary search markers. */
  519.         public int restartInterval() {
  520.             return restartInterval;
  521.         }

  522.         /** @return smallest update index contained in this reftable. */
  523.         public long minUpdateIndex() {
  524.             return minUpdateIndex;
  525.         }

  526.         /** @return largest update index contained in this reftable. */
  527.         public long maxUpdateIndex() {
  528.             return maxUpdateIndex;
  529.         }

  530.         /** @return total number of references in the reftable. */
  531.         public long refCount() {
  532.             return refCnt;
  533.         }

  534.         /** @return number of unique objects in the reftable. */
  535.         public long objCount() {
  536.             return objCnt;
  537.         }

  538.         /** @return total number of log records in the reftable. */
  539.         public long logCount() {
  540.             return logCnt;
  541.         }

  542.         /** @return number of bytes for references, including ref index. */
  543.         public long refBytes() {
  544.             return refBytes;
  545.         }

  546.         /** @return number of bytes for objects, including object index. */
  547.         public long objBytes() {
  548.             return objBytes;
  549.         }

  550.         /** @return number of bytes for log, including log index. */
  551.         public long logBytes() {
  552.             return logBytes;
  553.         }

  554.         /** @return total number of bytes in the reftable. */
  555.         public long totalBytes() {
  556.             return totalBytes;
  557.         }

  558.         /** @return bytes of padding used to maintain block alignment. */
  559.         public long paddingBytes() {
  560.             return paddingUsed;
  561.         }

  562.         /** @return number of bytes in the ref index; 0 if no index was used. */
  563.         public int refIndexSize() {
  564.             return refIndexSize;
  565.         }

  566.         /** @return number of levels in the ref index. */
  567.         public int refIndexLevels() {
  568.             return refIndexLevels;
  569.         }

  570.         /** @return number of bytes in the object index; 0 if no index. */
  571.         public int objIndexSize() {
  572.             return objIndexSize;
  573.         }

  574.         /** @return number of levels in the object index. */
  575.         public int objIndexLevels() {
  576.             return objIndexLevels;
  577.         }

  578.         /**
  579.          * @return number of bytes required to uniquely identify all objects in
  580.          *         the reftable. Unique abbreviations in hex would be
  581.          *         {@code 2 * objIdLength()}.
  582.          */
  583.         public int objIdLength() {
  584.             return objIdLen;
  585.         }
  586.     }

  587.     private static List<RefList> sortById(ObjectIdSubclassMap<RefList> m) {
  588.         List<RefList> s = new ArrayList<>(m.size());
  589.         for (RefList l : m) {
  590.             s.add(l);
  591.         }
  592.         Collections.sort(s);
  593.         return s;
  594.     }

  595.     private static int shortestUniqueAbbreviation(List<RefList> in) {
  596.         // Estimate minimum number of bytes necessary for unique abbreviations.
  597.         int bytes = Math.max(2, (int) (log(in.size()) / log(8)));
  598.         Set<AbbreviatedObjectId> tmp = new HashSet<>((int) (in.size() * 0.75f));
  599.         retry: for (;;) {
  600.             int hexLen = bytes * 2;
  601.             for (ObjectId id : in) {
  602.                 AbbreviatedObjectId a = id.abbreviate(hexLen);
  603.                 if (!tmp.add(a)) {
  604.                     if (++bytes >= OBJECT_ID_LENGTH) {
  605.                         return OBJECT_ID_LENGTH;
  606.                     }
  607.                     tmp.clear();
  608.                     continue retry;
  609.                 }
  610.             }
  611.             return bytes;
  612.         }
  613.     }

  614.     private static class RefList extends ObjectIdOwnerMap.Entry {
  615.         final LongList blockPos = new LongList(2);

  616.         RefList(AnyObjectId id) {
  617.             super(id);
  618.         }

  619.         void addBlock(long pos) {
  620.             if (!blockPos.contains(pos)) {
  621.                 blockPos.add(pos);
  622.             }
  623.         }
  624.     }

  625.     private class Section {
  626.         final IndexBuilder idx;
  627.         final long firstBlockPosition;

  628.         long entryCnt;
  629.         long bytes;

  630.         Section(byte keyType) {
  631.             idx = new IndexBuilder(keyType);
  632.             firstBlockPosition = out.size();
  633.         }

  634.         long write(BlockWriter.Entry entry) throws IOException {
  635.             if (cur == null) {
  636.                 beginBlock(entry);
  637.             } else if (!cur.tryAdd(entry)) {
  638.                 flushCurBlock();
  639.                 if (cur.padBetweenBlocks()) {
  640.                     out.padBetweenBlocksToNextBlock();
  641.                 }
  642.                 beginBlock(entry);
  643.             }
  644.             entryCnt++;
  645.             return out.size();
  646.         }

  647.         private void beginBlock(BlockWriter.Entry entry)
  648.                 throws BlockSizeTooSmallException {
  649.             byte blockType = entry.blockType();
  650.             int bs = out.bytesAvailableInBlock();
  651.             cur = new BlockWriter(blockType, idx.keyType, bs, restartInterval);
  652.             cur.mustAdd(entry);
  653.         }

  654.         void flushCurBlock() throws IOException {
  655.             idx.entries.add(new IndexEntry(cur.lastKey(), out.size()));
  656.             cur.writeTo(out);
  657.         }

  658.         void finishSectionMaybeWriteIndex() throws IOException {
  659.             flushCurBlock();
  660.             cur = null;
  661.             if (shouldHaveIndex(idx)) {
  662.                 idx.writeIndex();
  663.             }
  664.             bytes = out.size() - firstBlockPosition;
  665.         }
  666.     }

  667.     private class IndexBuilder {
  668.         final byte keyType;
  669.         List<IndexEntry> entries = new ArrayList<>();
  670.         long rootPosition;
  671.         int bytes;
  672.         int levels;

  673.         IndexBuilder(byte kt) {
  674.             keyType = kt;
  675.         }

  676.         int estimateBytes(long curBlockPos) {
  677.             BlockWriter b = new BlockWriter(
  678.                     INDEX_BLOCK_TYPE, keyType,
  679.                     MAX_BLOCK_SIZE,
  680.                     Math.max(restartInterval, entries.size() / MAX_RESTARTS));
  681.             try {
  682.                 for (Entry e : entries) {
  683.                     b.mustAdd(e);
  684.                 }
  685.                 if (cur != null) {
  686.                     b.mustAdd(new IndexEntry(cur.lastKey(), curBlockPos));
  687.                 }
  688.             } catch (BlockSizeTooSmallException e) {
  689.                 return b.currentSize();
  690.             }
  691.             return b.currentSize();
  692.         }

  693.         void writeIndex() throws IOException {
  694.             if (padBetweenBlocks(keyType)) {
  695.                 out.padBetweenBlocksToNextBlock();
  696.             }
  697.             long startPos = out.size();
  698.             writeMultiLevelIndex(entries);
  699.             bytes = (int) (out.size() - startPos);
  700.             entries = null;
  701.         }

  702.         private void writeMultiLevelIndex(List<IndexEntry> keys)
  703.                 throws IOException {
  704.             levels = 1;
  705.             while (maxIndexLevels == 0 || levels < maxIndexLevels) {
  706.                 keys = writeOneLevel(keys);
  707.                 if (keys == null) {
  708.                     return;
  709.                 }
  710.                 levels++;
  711.             }

  712.             // When maxIndexLevels has restricted the writer, write one
  713.             // index block with the entire remaining set of keys.
  714.             BlockWriter b = new BlockWriter(
  715.                     INDEX_BLOCK_TYPE, keyType,
  716.                     MAX_BLOCK_SIZE,
  717.                     Math.max(restartInterval, keys.size() / MAX_RESTARTS));
  718.             for (Entry e : keys) {
  719.                 b.mustAdd(e);
  720.             }
  721.             rootPosition = out.size();
  722.             b.writeTo(out);
  723.         }

  724.         private List<IndexEntry> writeOneLevel(List<IndexEntry> keys)
  725.                 throws IOException {
  726.             Section thisLevel = new Section(keyType);
  727.             for (Entry e : keys) {
  728.                 thisLevel.write(e);
  729.             }
  730.             if (!thisLevel.idx.entries.isEmpty()) {
  731.                 thisLevel.flushCurBlock();
  732.                 if (cur.padBetweenBlocks()) {
  733.                     out.padBetweenBlocksToNextBlock();
  734.                 }
  735.                 cur = null;
  736.                 return thisLevel.idx.entries;
  737.             }

  738.             // The current block fit entire level; make it the root.
  739.             rootPosition = out.size();
  740.             cur.writeTo(out);
  741.             cur = null;
  742.             return null;
  743.         }
  744.     }
  745. }