View Javadoc
1   /*
2    * Copyright (C) 2017, 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  
44  package org.eclipse.jgit.internal.storage.reftable;
45  
46  import static java.lang.Math.log;
47  import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.padBetweenBlocks;
48  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN;
49  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
50  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_MAGIC;
51  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
52  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
53  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_BLOCK_SIZE;
54  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS;
55  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
56  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
57  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1;
58  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
59  
60  import java.io.IOException;
61  import java.io.OutputStream;
62  import java.util.ArrayList;
63  import java.util.Collection;
64  import java.util.Collections;
65  import java.util.HashSet;
66  import java.util.Iterator;
67  import java.util.List;
68  import java.util.Set;
69  import java.util.zip.CRC32;
70  
71  import org.eclipse.jgit.annotations.Nullable;
72  import org.eclipse.jgit.internal.storage.reftable.BlockWriter.DeleteLogEntry;
73  import org.eclipse.jgit.internal.storage.reftable.BlockWriter.Entry;
74  import org.eclipse.jgit.internal.storage.reftable.BlockWriter.IndexEntry;
75  import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry;
76  import org.eclipse.jgit.internal.storage.reftable.BlockWriter.ObjEntry;
77  import org.eclipse.jgit.internal.storage.reftable.BlockWriter.RefEntry;
78  import org.eclipse.jgit.lib.AbbreviatedObjectId;
79  import org.eclipse.jgit.lib.AnyObjectId;
80  import org.eclipse.jgit.lib.ObjectId;
81  import org.eclipse.jgit.lib.ObjectIdOwnerMap;
82  import org.eclipse.jgit.lib.ObjectIdSubclassMap;
83  import org.eclipse.jgit.lib.PersonIdent;
84  import org.eclipse.jgit.lib.Ref;
85  import org.eclipse.jgit.util.LongList;
86  import org.eclipse.jgit.util.NB;
87  
88  /**
89   * Writes a reftable formatted file.
90   * <p>
91   * A reftable can be written in a streaming fashion, provided the caller sorts
92   * all references. A
93   * {@link org.eclipse.jgit.internal.storage.reftable.ReftableWriter} is
94   * single-use, and not thread-safe.
95   */
96  public class ReftableWriter {
97  	private ReftableConfig config;
98  	private int refBlockSize;
99  	private int logBlockSize;
100 	private int restartInterval;
101 	private int maxIndexLevels;
102 	private boolean alignBlocks;
103 	private boolean indexObjects;
104 
105 	private long minUpdateIndex;
106 	private long maxUpdateIndex;
107 
108 	private ReftableOutputStream out;
109 	private ObjectIdSubclassMap<RefList> obj2ref;
110 
111 	private BlockWriter cur;
112 	private Section refs;
113 	private Section objs;
114 	private Section logs;
115 	private int objIdLen;
116 	private Stats stats;
117 
118 	/**
119 	 * Initialize a writer with a default configuration.
120 	 */
121 	public ReftableWriter() {
122 		this(new ReftableConfig());
123 	}
124 
125 	/**
126 	 * Initialize a writer with a specific configuration.
127 	 *
128 	 * @param cfg
129 	 *            configuration for the writer.
130 	 */
131 	public ReftableWriter(ReftableConfig cfg) {
132 		config = cfg;
133 	}
134 
135 	/**
136 	 * Set configuration for the writer.
137 	 *
138 	 * @param cfg
139 	 *            configuration for the writer.
140 	 * @return {@code this}
141 	 */
142 	public ReftableWriter setConfig(ReftableConfig cfg) {
143 		this.config = cfg != null ? cfg : new ReftableConfig();
144 		return this;
145 	}
146 
147 	/**
148 	 * Set the minimum update index for log entries that appear in this
149 	 * reftable.
150 	 *
151 	 * @param min
152 	 *            the minimum update index for log entries that appear in this
153 	 *            reftable. This should be 1 higher than the prior reftable's
154 	 *            {@code maxUpdateIndex} if this table will be used in a stack.
155 	 * @return {@code this}
156 	 */
157 	public ReftableWriter setMinUpdateIndex(long min) {
158 		minUpdateIndex = min;
159 		return this;
160 	}
161 
162 	/**
163 	 * Set the maximum update index for log entries that appear in this
164 	 * reftable.
165 	 *
166 	 * @param max
167 	 *            the maximum update index for log entries that appear in this
168 	 *            reftable. This should be at least 1 higher than the prior
169 	 *            reftable's {@code maxUpdateIndex} if this table will be used
170 	 *            in a stack.
171 	 * @return {@code this}
172 	 */
173 	public ReftableWriter setMaxUpdateIndex(long max) {
174 		maxUpdateIndex = max;
175 		return this;
176 	}
177 
178 	/**
179 	 * Begin writing the reftable.
180 	 *
181 	 * @param os
182 	 *            stream to write the table to. Caller is responsible for
183 	 *            closing the stream after invoking {@link #finish()}.
184 	 * @return {@code this}
185 	 * @throws java.io.IOException
186 	 *             if reftable header cannot be written.
187 	 */
188 	public ReftableWriter begin(OutputStream os) throws IOException {
189 		refBlockSize = config.getRefBlockSize();
190 		logBlockSize = config.getLogBlockSize();
191 		restartInterval = config.getRestartInterval();
192 		maxIndexLevels = config.getMaxIndexLevels();
193 		alignBlocks = config.isAlignBlocks();
194 		indexObjects = config.isIndexObjects();
195 
196 		if (refBlockSize <= 0) {
197 			refBlockSize = 4 << 10;
198 		} else if (refBlockSize > MAX_BLOCK_SIZE) {
199 			throw new IllegalArgumentException();
200 		}
201 		if (logBlockSize <= 0) {
202 			logBlockSize = 2 * refBlockSize;
203 		}
204 		if (restartInterval <= 0) {
205 			restartInterval = refBlockSize < (60 << 10) ? 16 : 64;
206 		}
207 
208 		out = new ReftableOutputStream(os, refBlockSize, alignBlocks);
209 		refs = new Section(REF_BLOCK_TYPE);
210 		if (indexObjects) {
211 			obj2ref = new ObjectIdSubclassMap<>();
212 		}
213 		writeFileHeader();
214 		return this;
215 	}
216 
217 	/**
218 	 * Sort a collection of references and write them to the reftable.
219 	 *
220 	 * @param refsToPack
221 	 *            references to sort and write.
222 	 * @return {@code this}
223 	 * @throws java.io.IOException
224 	 *             if reftable cannot be written.
225 	 */
226 	public ReftableWriter sortAndWriteRefs(Collection<Ref> refsToPack)
227 			throws IOException {
228 		Iterator<RefEntry> itr = refsToPack.stream()
229 				.map(r -> new RefEntry(r, maxUpdateIndex - minUpdateIndex))
230 				.sorted(Entry::compare)
231 				.iterator();
232 		while (itr.hasNext()) {
233 			RefEntry entry = itr.next();
234 			long blockPos = refs.write(entry);
235 			indexRef(entry.ref, blockPos);
236 		}
237 		return this;
238 	}
239 
240 	/**
241 	 * Write one reference to the reftable.
242 	 * <p>
243 	 * References must be passed in sorted order.
244 	 *
245 	 * @param ref
246 	 *            the reference to store.
247 	 * @throws java.io.IOException
248 	 *             if reftable cannot be written.
249 	 */
250 	public void writeRef(Ref ref) throws IOException {
251 		writeRef(ref, maxUpdateIndex);
252 	}
253 
254 	/**
255 	 * Write one reference to the reftable.
256 	 * <p>
257 	 * References must be passed in sorted order.
258 	 *
259 	 * @param ref
260 	 *            the reference to store.
261 	 * @param updateIndex
262 	 *            the updateIndex that modified this reference. Must be
263 	 *            {@code >= minUpdateIndex} for this file.
264 	 * @throws java.io.IOException
265 	 *             if reftable cannot be written.
266 	 */
267 	public void writeRef(Ref ref, long updateIndex) throws IOException {
268 		if (updateIndex < minUpdateIndex) {
269 			throw new IllegalArgumentException();
270 		}
271 		long d = updateIndex - minUpdateIndex;
272 		long blockPos = refs.write(new RefEntry(ref, d));
273 		indexRef(ref, blockPos);
274 	}
275 
276 	private void indexRef(Ref ref, long blockPos) {
277 		if (indexObjects && !ref.isSymbolic()) {
278 			indexId(ref.getObjectId(), blockPos);
279 			indexId(ref.getPeeledObjectId(), blockPos);
280 		}
281 	}
282 
283 	private void indexId(ObjectId id, long blockPos) {
284 		if (id != null) {
285 			RefList l = obj2ref.get(id);
286 			if (l == null) {
287 				l = new RefList(id);
288 				obj2ref.add(l);
289 			}
290 			l.addBlock(blockPos);
291 		}
292 	}
293 
294 	/**
295 	 * Write one reflog entry to the reftable.
296 	 * <p>
297 	 * Reflog entries must be written in reference name and descending
298 	 * {@code updateIndex} (highest first) order.
299 	 *
300 	 * @param ref
301 	 *            name of the reference.
302 	 * @param updateIndex
303 	 *            identifier of the transaction that created the log record. The
304 	 *            {@code updateIndex} must be unique within the scope of
305 	 *            {@code ref}, and must be within the bounds defined by
306 	 *            {@code minUpdateIndex <= updateIndex <= maxUpdateIndex}.
307 	 * @param who
308 	 *            committer of the reflog entry.
309 	 * @param oldId
310 	 *            prior id; pass {@link org.eclipse.jgit.lib.ObjectId#zeroId()}
311 	 *            for creations.
312 	 * @param newId
313 	 *            new id; pass {@link org.eclipse.jgit.lib.ObjectId#zeroId()}
314 	 *            for deletions.
315 	 * @param message
316 	 *            optional message (may be null).
317 	 * @throws java.io.IOException
318 	 *             if reftable cannot be written.
319 	 */
320 	public void writeLog(String ref, long updateIndex, PersonIdent who,
321 			ObjectId oldId, ObjectId newId, @Nullable String message)
322 					throws IOException {
323 		String msg = message != null ? message : ""; //$NON-NLS-1$
324 		beginLog();
325 		logs.write(new LogEntry(ref, updateIndex, who, oldId, newId, msg));
326 	}
327 
328 	/**
329 	 * Record deletion of one reflog entry in this reftable.
330 	 *
331 	 * <p>
332 	 * The deletion can shadow an entry stored in a lower table in the stack.
333 	 * This is useful for {@code refs/stash} and dropping an entry from its
334 	 * reflog.
335 	 * <p>
336 	 * Deletion must be properly interleaved in sorted updateIndex order with
337 	 * any other logs written by
338 	 * {@link #writeLog(String, long, PersonIdent, ObjectId, ObjectId, String)}.
339 	 *
340 	 * @param ref
341 	 *            the ref to delete (hide) a reflog entry from.
342 	 * @param updateIndex
343 	 *            the update index that must be hidden.
344 	 * @throws java.io.IOException
345 	 *             if reftable cannot be written.
346 	 */
347 	public void deleteLog(String ref, long updateIndex) throws IOException {
348 		beginLog();
349 		logs.write(new DeleteLogEntry(ref, updateIndex));
350 	}
351 
352 	private void beginLog() throws IOException {
353 		if (logs == null) {
354 			finishRefAndObjSections(); // close prior ref blocks and their index, if present.
355 			out.flushFileHeader();
356 			out.setBlockSize(logBlockSize);
357 			logs = new Section(LOG_BLOCK_TYPE);
358 		}
359 	}
360 
361 	/**
362 	 * Get an estimate of the current size in bytes of the reftable
363 	 *
364 	 * @return an estimate of the current size in bytes of the reftable, if it
365 	 *         was finished right now. Estimate is only accurate if
366 	 *         {@link org.eclipse.jgit.internal.storage.reftable.ReftableConfig#setIndexObjects(boolean)}
367 	 *         is {@code false} and
368 	 *         {@link org.eclipse.jgit.internal.storage.reftable.ReftableConfig#setMaxIndexLevels(int)}
369 	 *         is {@code 1}.
370 	 */
371 	public long estimateTotalBytes() {
372 		long bytes = out.size();
373 		if (bytes == 0) {
374 			bytes += FILE_HEADER_LEN;
375 		}
376 		if (cur != null) {
377 			long curBlockPos = out.size();
378 			int sz = cur.currentSize();
379 			bytes += sz;
380 
381 			IndexBuilder idx = null;
382 			if (cur.blockType() == REF_BLOCK_TYPE) {
383 				idx = refs.idx;
384 			} else if (cur.blockType() == LOG_BLOCK_TYPE) {
385 				idx = logs.idx;
386 			}
387 			if (idx != null && shouldHaveIndex(idx)) {
388 				if (idx == refs.idx) {
389 					bytes += out.estimatePadBetweenBlocks(sz);
390 				}
391 				bytes += idx.estimateBytes(curBlockPos);
392 			}
393 		}
394 		bytes += FILE_FOOTER_LEN;
395 		return bytes;
396 	}
397 
398 	/**
399 	 * Finish writing the reftable by writing its trailer.
400 	 *
401 	 * @return {@code this}
402 	 * @throws java.io.IOException
403 	 *             if reftable cannot be written.
404 	 */
405 	public ReftableWriter finish() throws IOException {
406 		finishRefAndObjSections();
407 		finishLogSection();
408 		writeFileFooter();
409 		out.finishFile();
410 
411 		stats = new Stats(this, out);
412 		out = null;
413 		obj2ref = null;
414 		cur = null;
415 		refs = null;
416 		objs = null;
417 		logs = null;
418 		return this;
419 	}
420 
421 	private void finishRefAndObjSections() throws IOException {
422 		if (cur != null && cur.blockType() == REF_BLOCK_TYPE) {
423 			refs.finishSectionMaybeWriteIndex();
424 			if (indexObjects && !obj2ref.isEmpty() && refs.idx.bytes > 0) {
425 				writeObjBlocks();
426 			}
427 			obj2ref = null;
428 		}
429 	}
430 
431 	private void writeObjBlocks() throws IOException {
432 		List<RefList> sorted = sortById(obj2ref);
433 		obj2ref = null;
434 		objIdLen = shortestUniqueAbbreviation(sorted);
435 
436 		out.padBetweenBlocksToNextBlock();
437 		objs = new Section(OBJ_BLOCK_TYPE);
438 		objs.entryCnt = sorted.size();
439 		for (RefList l : sorted) {
440 			objs.write(new ObjEntry(objIdLen, l, l.blockPos));
441 		}
442 		objs.finishSectionMaybeWriteIndex();
443 	}
444 
445 	private void finishLogSection() throws IOException {
446 		if (cur != null && cur.blockType() == LOG_BLOCK_TYPE) {
447 			logs.finishSectionMaybeWriteIndex();
448 		}
449 	}
450 
451 	private boolean shouldHaveIndex(IndexBuilder idx) {
452 		int threshold;
453 		if (idx == refs.idx && alignBlocks) {
454 			threshold = 4;
455 		} else {
456 			threshold = 1;
457 		}
458 		return idx.entries.size() + (cur != null ? 1 : 0) > threshold;
459 	}
460 
461 	private void writeFileHeader() {
462 		byte[] hdr = new byte[FILE_HEADER_LEN];
463 		encodeHeader(hdr);
464 		out.write(hdr, 0, FILE_HEADER_LEN);
465 	}
466 
467 	private void encodeHeader(byte[] hdr) {
468 		System.arraycopy(FILE_HEADER_MAGIC, 0, hdr, 0, 4);
469 		int bs = alignBlocks ? refBlockSize : 0;
470 		NB.encodeInt32(hdr, 4, (VERSION_1 << 24) | bs);
471 		NB.encodeInt64(hdr, 8, minUpdateIndex);
472 		NB.encodeInt64(hdr, 16, maxUpdateIndex);
473 	}
474 
475 	private void writeFileFooter() {
476 		int ftrLen = FILE_FOOTER_LEN;
477 		byte[] ftr = new byte[ftrLen];
478 		encodeHeader(ftr);
479 
480 		NB.encodeInt64(ftr, 24, indexPosition(refs));
481 		NB.encodeInt64(ftr, 32, (firstBlockPosition(objs) << 5) | objIdLen);
482 		NB.encodeInt64(ftr, 40, indexPosition(objs));
483 		NB.encodeInt64(ftr, 48, firstBlockPosition(logs));
484 		NB.encodeInt64(ftr, 56, indexPosition(logs));
485 
486 		CRC32 crc = new CRC32();
487 		crc.update(ftr, 0, ftrLen - 4);
488 		NB.encodeInt32(ftr, ftrLen - 4, (int) crc.getValue());
489 
490 		out.write(ftr, 0, ftrLen);
491 	}
492 
493 	private static long firstBlockPosition(@Nullable Section s) {
494 		return s != null ? s.firstBlockPosition : 0;
495 	}
496 
497 	private static long indexPosition(@Nullable Section s) {
498 		return s != null && s.idx != null ? s.idx.rootPosition : 0;
499 	}
500 
501 	/**
502 	 * Get statistics of the last written reftable.
503 	 *
504 	 * @return statistics of the last written reftable.
505 	 */
506 	public Stats getStats() {
507 		return stats;
508 	}
509 
510 	/** Statistics about a written reftable. */
511 	public static class Stats {
512 		private final int refBlockSize;
513 		private final int logBlockSize;
514 		private final int restartInterval;
515 
516 		private final long minUpdateIndex;
517 		private final long maxUpdateIndex;
518 
519 		private final long refCnt;
520 		private final long objCnt;
521 		private final int objIdLen;
522 		private final long logCnt;
523 		private final long refBytes;
524 		private final long objBytes;
525 		private final long logBytes;
526 		private final long paddingUsed;
527 		private final long totalBytes;
528 
529 		private final int refIndexSize;
530 		private final int refIndexLevels;
531 		private final int objIndexSize;
532 		private final int objIndexLevels;
533 
534 		Stats(ReftableWriter w, ReftableOutputStream o) {
535 			refBlockSize = w.refBlockSize;
536 			logBlockSize = w.logBlockSize;
537 			restartInterval = w.restartInterval;
538 
539 			minUpdateIndex = w.minUpdateIndex;
540 			maxUpdateIndex = w.maxUpdateIndex;
541 			paddingUsed = o.paddingUsed();
542 			totalBytes = o.size();
543 
544 			refCnt = w.refs.entryCnt;
545 			refBytes = w.refs.bytes;
546 
547 			objCnt = w.objs != null ? w.objs.entryCnt : 0;
548 			objBytes = w.objs != null ? w.objs.bytes : 0;
549 			objIdLen = w.objIdLen;
550 
551 			logCnt = w.logs != null ? w.logs.entryCnt : 0;
552 			logBytes = w.logs != null ? w.logs.bytes : 0;
553 
554 			IndexBuilder refIdx = w.refs.idx;
555 			refIndexSize = refIdx.bytes;
556 			refIndexLevels = refIdx.levels;
557 
558 			IndexBuilder objIdx = w.objs != null ? w.objs.idx : null;
559 			objIndexSize = objIdx != null ? objIdx.bytes : 0;
560 			objIndexLevels = objIdx != null ? objIdx.levels : 0;
561 		}
562 
563 		/** @return number of bytes in a ref block. */
564 		public int refBlockSize() {
565 			return refBlockSize;
566 		}
567 
568 		/** @return number of bytes to compress into a log block. */
569 		public int logBlockSize() {
570 			return logBlockSize;
571 		}
572 
573 		/** @return number of references between binary search markers. */
574 		public int restartInterval() {
575 			return restartInterval;
576 		}
577 
578 		/** @return smallest update index contained in this reftable. */
579 		public long minUpdateIndex() {
580 			return minUpdateIndex;
581 		}
582 
583 		/** @return largest update index contained in this reftable. */
584 		public long maxUpdateIndex() {
585 			return maxUpdateIndex;
586 		}
587 
588 		/** @return total number of references in the reftable. */
589 		public long refCount() {
590 			return refCnt;
591 		}
592 
593 		/** @return number of unique objects in the reftable. */
594 		public long objCount() {
595 			return objCnt;
596 		}
597 
598 		/** @return total number of log records in the reftable. */
599 		public long logCount() {
600 			return logCnt;
601 		}
602 
603 		/** @return number of bytes for references, including ref index. */
604 		public long refBytes() {
605 			return refBytes;
606 		}
607 
608 		/** @return number of bytes for objects, including object index. */
609 		public long objBytes() {
610 			return objBytes;
611 		}
612 
613 		/** @return number of bytes for log, including log index. */
614 		public long logBytes() {
615 			return logBytes;
616 		}
617 
618 		/** @return total number of bytes in the reftable. */
619 		public long totalBytes() {
620 			return totalBytes;
621 		}
622 
623 		/** @return bytes of padding used to maintain block alignment. */
624 		public long paddingBytes() {
625 			return paddingUsed;
626 		}
627 
628 		/** @return number of bytes in the ref index; 0 if no index was used. */
629 		public int refIndexSize() {
630 			return refIndexSize;
631 		}
632 
633 		/** @return number of levels in the ref index. */
634 		public int refIndexLevels() {
635 			return refIndexLevels;
636 		}
637 
638 		/** @return number of bytes in the object index; 0 if no index. */
639 		public int objIndexSize() {
640 			return objIndexSize;
641 		}
642 
643 		/** @return number of levels in the object index. */
644 		public int objIndexLevels() {
645 			return objIndexLevels;
646 		}
647 
648 		/**
649 		 * @return number of bytes required to uniquely identify all objects in
650 		 *         the reftable. Unique abbreviations in hex would be
651 		 *         {@code 2 * objIdLength()}.
652 		 */
653 		public int objIdLength() {
654 			return objIdLen;
655 		}
656 	}
657 
658 	private static List<RefList> sortById(ObjectIdSubclassMap<RefList> m) {
659 		List<RefList> s = new ArrayList<>(m.size());
660 		for (RefList l : m) {
661 			s.add(l);
662 		}
663 		Collections.sort(s);
664 		return s;
665 	}
666 
667 	private static int shortestUniqueAbbreviation(List<RefList> in) {
668 		// Estimate minimum number of bytes necessary for unique abbreviations.
669 		int bytes = Math.max(2, (int) (log(in.size()) / log(8)));
670 		Set<AbbreviatedObjectId> tmp = new HashSet<>((int) (in.size() * 0.75f));
671 		retry: for (;;) {
672 			int hexLen = bytes * 2;
673 			for (ObjectId id : in) {
674 				AbbreviatedObjectId a = id.abbreviate(hexLen);
675 				if (!tmp.add(a)) {
676 					if (++bytes >= OBJECT_ID_LENGTH) {
677 						return OBJECT_ID_LENGTH;
678 					}
679 					tmp.clear();
680 					continue retry;
681 				}
682 			}
683 			return bytes;
684 		}
685 	}
686 
687 	private static class RefList extends ObjectIdOwnerMap.Entry {
688 		final LongListList.html#LongList">LongList blockPos = new LongList(2);
689 
690 		RefList(AnyObjectId id) {
691 			super(id);
692 		}
693 
694 		void addBlock(long pos) {
695 			if (!blockPos.contains(pos)) {
696 				blockPos.add(pos);
697 			}
698 		}
699 	}
700 
701 	private class Section {
702 		final IndexBuilder idx;
703 		final long firstBlockPosition;
704 
705 		long entryCnt;
706 		long bytes;
707 
708 		Section(byte keyType) {
709 			idx = new IndexBuilder(keyType);
710 			firstBlockPosition = out.size();
711 		}
712 
713 		long write(BlockWriter.Entry entry) throws IOException {
714 			if (cur == null) {
715 				beginBlock(entry);
716 			} else if (!cur.tryAdd(entry)) {
717 				flushCurBlock();
718 				if (cur.padBetweenBlocks()) {
719 					out.padBetweenBlocksToNextBlock();
720 				}
721 				beginBlock(entry);
722 			}
723 			entryCnt++;
724 			return out.size();
725 		}
726 
727 		private void beginBlock(BlockWriter.Entry entry)
728 				throws BlockSizeTooSmallException {
729 			byte blockType = entry.blockType();
730 			int bs = out.bytesAvailableInBlock();
731 			cur = new BlockWriter(blockType, idx.keyType, bs, restartInterval);
732 			cur.mustAdd(entry);
733 		}
734 
735 		void flushCurBlock() throws IOException {
736 			idx.entries.add(new IndexEntry(cur.lastKey(), out.size()));
737 			cur.writeTo(out);
738 		}
739 
740 		void finishSectionMaybeWriteIndex() throws IOException {
741 			flushCurBlock();
742 			cur = null;
743 			if (shouldHaveIndex(idx)) {
744 				idx.writeIndex();
745 			}
746 			bytes = out.size() - firstBlockPosition;
747 		}
748 	}
749 
750 	private class IndexBuilder {
751 		final byte keyType;
752 		List<IndexEntry> entries = new ArrayList<>();
753 		long rootPosition;
754 		int bytes;
755 		int levels;
756 
757 		IndexBuilder(byte kt) {
758 			keyType = kt;
759 		}
760 
761 		int estimateBytes(long curBlockPos) {
762 			BlockWriter b = new BlockWriter(
763 					INDEX_BLOCK_TYPE, keyType,
764 					MAX_BLOCK_SIZE,
765 					Math.max(restartInterval, entries.size() / MAX_RESTARTS));
766 			try {
767 				for (Entry e : entries) {
768 					b.mustAdd(e);
769 				}
770 				if (cur != null) {
771 					b.mustAdd(new IndexEntry(cur.lastKey(), curBlockPos));
772 				}
773 			} catch (BlockSizeTooSmallException e) {
774 				return b.currentSize();
775 			}
776 			return b.currentSize();
777 		}
778 
779 		void writeIndex() throws IOException {
780 			if (padBetweenBlocks(keyType)) {
781 				out.padBetweenBlocksToNextBlock();
782 			}
783 			long startPos = out.size();
784 			writeMultiLevelIndex(entries);
785 			bytes = (int) (out.size() - startPos);
786 			entries = null;
787 		}
788 
789 		private void writeMultiLevelIndex(List<IndexEntry> keys)
790 				throws IOException {
791 			levels = 1;
792 			while (maxIndexLevels == 0 || levels < maxIndexLevels) {
793 				keys = writeOneLevel(keys);
794 				if (keys == null) {
795 					return;
796 				}
797 				levels++;
798 			}
799 
800 			// When maxIndexLevels has restricted the writer, write one
801 			// index block with the entire remaining set of keys.
802 			BlockWriter b = new BlockWriter(
803 					INDEX_BLOCK_TYPE, keyType,
804 					MAX_BLOCK_SIZE,
805 					Math.max(restartInterval, keys.size() / MAX_RESTARTS));
806 			for (Entry e : keys) {
807 				b.mustAdd(e);
808 			}
809 			rootPosition = out.size();
810 			b.writeTo(out);
811 		}
812 
813 		private List<IndexEntry> writeOneLevel(List<IndexEntry> keys)
814 				throws IOException {
815 			Section thisLevel = new Section(keyType);
816 			for (Entry e : keys) {
817 				thisLevel.write(e);
818 			}
819 			if (!thisLevel.idx.entries.isEmpty()) {
820 				thisLevel.flushCurBlock();
821 				if (cur.padBetweenBlocks()) {
822 					out.padBetweenBlocksToNextBlock();
823 				}
824 				cur = null;
825 				return thisLevel.idx.entries;
826 			}
827 
828 			// The current block fit entire level; make it the root.
829 			rootPosition = out.size();
830 			cur.writeTo(out);
831 			cur = null;
832 			return null;
833 		}
834 	}
835 }