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