ReftableOutputStream.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.nio.charset.StandardCharsets.UTF_8;
  12. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
  13. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
  14. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
  15. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;

  16. import java.io.IOException;
  17. import java.io.OutputStream;
  18. import java.util.Arrays;
  19. import java.util.zip.Deflater;
  20. import java.util.zip.DeflaterOutputStream;

  21. import org.eclipse.jgit.internal.JGitText;
  22. import org.eclipse.jgit.lib.ObjectId;
  23. import org.eclipse.jgit.util.NB;
  24. import org.eclipse.jgit.util.io.CountingOutputStream;

  25. /**
  26.  * Wrapper to assist formatting a reftable to an {@link OutputStream}.
  27.  * <p>
  28.  * Internally buffers at block size boundaries, flushing only complete blocks to
  29.  * the {@code OutputStream}.
  30.  */
  31. class ReftableOutputStream extends OutputStream {
  32.     private final byte[] tmp = new byte[10];
  33.     private final CountingOutputStream out;
  34.     private final boolean alignBlocks;

  35.     private Deflater deflater;
  36.     private DeflaterOutputStream compressor;

  37.     private int blockType;
  38.     private int blockSize;
  39.     private int blockStart;
  40.     private byte[] blockBuf;
  41.     private int cur;
  42.     private long paddingUsed;

  43.     ReftableOutputStream(OutputStream os, int bs, boolean align) {
  44.         blockSize = bs;
  45.         blockBuf = new byte[bs];
  46.         alignBlocks = align;
  47.         out = new CountingOutputStream(os);
  48.     }

  49.     void setBlockSize(int bs) {
  50.         blockSize = bs;
  51.     }

  52.     /** {@inheritDoc} */
  53.     @Override
  54.     public void write(int b) {
  55.         ensureBytesAvailableInBlockBuf(1);
  56.         blockBuf[cur++] = (byte) b;
  57.     }

  58.     /** {@inheritDoc} */
  59.     @Override
  60.     public void write(byte[] b, int off, int cnt) {
  61.         ensureBytesAvailableInBlockBuf(cnt);
  62.         System.arraycopy(b, off, blockBuf, cur, cnt);
  63.         cur += cnt;
  64.     }

  65.     int bytesWrittenInBlock() {
  66.         return cur;
  67.     }

  68.     int bytesAvailableInBlock() {
  69.         return blockSize - cur;
  70.     }

  71.     long paddingUsed() {
  72.         return paddingUsed;
  73.     }

  74.     /** @return bytes flushed; excludes {@link #bytesWrittenInBlock()}. */
  75.     long size() {
  76.         return out.getCount();
  77.     }

  78.     static int computeVarintSize(long val) {
  79.         int n = 1;
  80.         for (; (val >>>= 7) != 0; n++) {
  81.             val--;
  82.         }
  83.         return n;
  84.     }

  85.     void writeVarint(long val) {
  86.         int n = tmp.length;
  87.         tmp[--n] = (byte) (val & 0x7f);
  88.         while ((val >>>= 7) != 0) {
  89.             tmp[--n] = (byte) (0x80 | (--val & 0x7F));
  90.         }
  91.         write(tmp, n, tmp.length - n);
  92.     }

  93.     void writeInt16(int val) {
  94.         ensureBytesAvailableInBlockBuf(2);
  95.         NB.encodeInt16(blockBuf, cur, val);
  96.         cur += 2;
  97.     }

  98.     void writeInt24(int val) {
  99.         ensureBytesAvailableInBlockBuf(3);
  100.         NB.encodeInt24(blockBuf, cur, val);
  101.         cur += 3;
  102.     }

  103.     void writeId(ObjectId id) {
  104.         ensureBytesAvailableInBlockBuf(OBJECT_ID_LENGTH);
  105.         id.copyRawTo(blockBuf, cur);
  106.         cur += OBJECT_ID_LENGTH;
  107.     }

  108.     void writeVarintString(String s) {
  109.         writeVarintString(s.getBytes(UTF_8));
  110.     }

  111.     void writeVarintString(byte[] msg) {
  112.         writeVarint(msg.length);
  113.         write(msg, 0, msg.length);
  114.     }

  115.     private void ensureBytesAvailableInBlockBuf(int cnt) {
  116.         if (cur + cnt > blockBuf.length) {
  117.             int n = Math.max(cur + cnt, blockBuf.length * 2);
  118.             blockBuf = Arrays.copyOf(blockBuf, n);
  119.         }
  120.     }

  121.     void flushFileHeader() throws IOException {
  122.         if (cur == FILE_HEADER_LEN && out.getCount() == 0) {
  123.             out.write(blockBuf, 0, cur);
  124.             cur = 0;
  125.         }
  126.     }

  127.     void beginBlock(byte type) {
  128.         blockType = type;
  129.         blockStart = cur;
  130.         cur += 4; // reserve space for 4-byte block header.
  131.     }

  132.     void flushBlock() throws IOException {
  133.         if (cur > blockSize && blockType != INDEX_BLOCK_TYPE) {
  134.             throw new IOException(JGitText.get().overflowedReftableBlock);
  135.         }
  136.         NB.encodeInt32(blockBuf, blockStart, (blockType << 24) | cur);

  137.         if (blockType == LOG_BLOCK_TYPE) {
  138.             // Log blocks are deflated after the block header.
  139.             out.write(blockBuf, 0, 4);
  140.             if (deflater != null) {
  141.                 deflater.reset();
  142.             } else {
  143.                 deflater = new Deflater(Deflater.BEST_COMPRESSION);
  144.                 compressor = new DeflaterOutputStream(out, deflater);
  145.             }
  146.             compressor.write(blockBuf, 4, cur - 4);
  147.             compressor.finish();
  148.         } else {
  149.             // Other blocks are uncompressed.
  150.             out.write(blockBuf, 0, cur);
  151.         }

  152.         cur = 0;
  153.         blockType = 0;
  154.         blockStart = 0;
  155.     }

  156.     void padBetweenBlocksToNextBlock() throws IOException {
  157.         if (alignBlocks) {
  158.             long m = size() % blockSize;
  159.             if (m > 0) {
  160.                 int pad = blockSize - (int) m;
  161.                 ensureBytesAvailableInBlockBuf(pad);
  162.                 Arrays.fill(blockBuf, 0, pad, (byte) 0);
  163.                 out.write(blockBuf, 0, pad);
  164.                 paddingUsed += pad;
  165.             }
  166.         }
  167.     }

  168.     int estimatePadBetweenBlocks(int currentBlockSize) {
  169.         if (alignBlocks) {
  170.             long m = (size() + currentBlockSize) % blockSize;
  171.             return m > 0 ? blockSize - (int) m : 0;
  172.         }
  173.         return 0;
  174.     }

  175.     void finishFile() throws IOException {
  176.         // File footer doesn't need patching for the block start.
  177.         // Just flush what has been buffered.
  178.         out.write(blockBuf, 0, cur);
  179.         cur = 0;

  180.         if (deflater != null) {
  181.             deflater.end();
  182.         }
  183.     }
  184. }