PackOutputStream.java

  1. /*
  2.  * Copyright (C) 2008-2009, Google Inc.
  3.  * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.internal.storage.pack;

  12. import static org.eclipse.jgit.lib.Constants.OBJ_OFS_DELTA;
  13. import static org.eclipse.jgit.lib.Constants.OBJ_REF_DELTA;
  14. import static org.eclipse.jgit.lib.Constants.PACK_SIGNATURE;

  15. import java.io.IOException;
  16. import java.io.OutputStream;
  17. import java.security.MessageDigest;

  18. import org.eclipse.jgit.internal.JGitText;
  19. import org.eclipse.jgit.lib.Constants;
  20. import org.eclipse.jgit.lib.ProgressMonitor;
  21. import org.eclipse.jgit.util.NB;

  22. /**
  23.  * Custom output stream to support
  24.  * {@link org.eclipse.jgit.internal.storage.pack.PackWriter}.
  25.  */
  26. public final class PackOutputStream extends OutputStream {
  27.     private static final int BYTES_TO_WRITE_BEFORE_CANCEL_CHECK = 128 * 1024;

  28.     private final ProgressMonitor writeMonitor;

  29.     private final OutputStream out;

  30.     private final PackWriter packWriter;

  31.     private final MessageDigest md = Constants.newMessageDigest();

  32.     private long count;

  33.     private final byte[] headerBuffer = new byte[32];

  34.     private final byte[] copyBuffer = new byte[64 << 10];

  35.     private long checkCancelAt;

  36.     private boolean ofsDelta;

  37.     /**
  38.      * Initialize a pack output stream.
  39.      * <p>
  40.      * This constructor is exposed to support debugging the JGit library only.
  41.      * Application or storage level code should not create a PackOutputStream,
  42.      * instead use {@link org.eclipse.jgit.internal.storage.pack.PackWriter},
  43.      * and let the writer create the stream.
  44.      *
  45.      * @param writeMonitor
  46.      *            monitor to update on object output progress.
  47.      * @param out
  48.      *            target stream to receive all object contents.
  49.      * @param pw
  50.      *            packer that is going to perform the output.
  51.      */
  52.     public PackOutputStream(final ProgressMonitor writeMonitor,
  53.             final OutputStream out, final PackWriter pw) {
  54.         this.writeMonitor = writeMonitor;
  55.         this.out = out;
  56.         this.packWriter = pw;
  57.         this.checkCancelAt = BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
  58.     }

  59.     /** {@inheritDoc} */
  60.     @Override
  61.     public final void write(int b) throws IOException {
  62.         count++;
  63.         out.write(b);
  64.         md.update((byte) b);
  65.     }

  66.     /** {@inheritDoc} */
  67.     @Override
  68.     public final void write(byte[] b, int off, int len)
  69.             throws IOException {
  70.         while (0 < len) {
  71.             final int n = Math.min(len, BYTES_TO_WRITE_BEFORE_CANCEL_CHECK);
  72.             count += n;

  73.             if (checkCancelAt <= count) {
  74.                 if (writeMonitor.isCancelled()) {
  75.                     throw new IOException(
  76.                             JGitText.get().packingCancelledDuringObjectsWriting);
  77.                 }
  78.                 checkCancelAt = count + BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
  79.             }

  80.             out.write(b, off, n);
  81.             md.update(b, off, n);

  82.             off += n;
  83.             len -= n;
  84.         }
  85.     }

  86.     /** {@inheritDoc} */
  87.     @Override
  88.     public void flush() throws IOException {
  89.         out.flush();
  90.     }

  91.     final void writeFileHeader(int version, long objectCount)
  92.             throws IOException {
  93.         System.arraycopy(PACK_SIGNATURE, 0, headerBuffer, 0, 4);
  94.         NB.encodeInt32(headerBuffer, 4, version);
  95.         NB.encodeInt32(headerBuffer, 8, (int) objectCount);
  96.         write(headerBuffer, 0, 12);
  97.         ofsDelta = packWriter.isDeltaBaseAsOffset();
  98.     }

  99.     /**
  100.      * Write one object.
  101.      *
  102.      * If the object was already written, this method does nothing and returns
  103.      * quickly. This case occurs whenever an object was written out of order in
  104.      * order to ensure the delta base occurred before the object that needs it.
  105.      *
  106.      * @param otp
  107.      *            the object to write.
  108.      * @throws java.io.IOException
  109.      *             the object cannot be read from the object reader, or the
  110.      *             output stream is no longer accepting output. Caller must
  111.      *             examine the type of exception and possibly its message to
  112.      *             distinguish between these cases.
  113.      */
  114.     public final void writeObject(ObjectToPack otp) throws IOException {
  115.         packWriter.writeObject(this, otp);
  116.     }

  117.     /**
  118.      * Commits the object header onto the stream.
  119.      * <p>
  120.      * Once the header has been written, the object representation must be fully
  121.      * output, or packing must abort abnormally.
  122.      *
  123.      * @param otp
  124.      *            the object to pack. Header information is obtained.
  125.      * @param rawLength
  126.      *            number of bytes of the inflated content. For an object that is
  127.      *            in whole object format, this is the same as the object size.
  128.      *            For an object that is in a delta format, this is the size of
  129.      *            the inflated delta instruction stream.
  130.      * @throws java.io.IOException
  131.      *             the underlying stream refused to accept the header.
  132.      */
  133.     @SuppressWarnings("ShortCircuitBoolean")
  134.     public final void writeHeader(ObjectToPack otp, long rawLength)
  135.             throws IOException {
  136.         ObjectToPack b = otp.getDeltaBase();
  137.         if (b != null && (b.isWritten() & ofsDelta)) { // Non-short-circuit logic is intentional
  138.             int n = objectHeader(rawLength, OBJ_OFS_DELTA, headerBuffer);
  139.             n = ofsDelta(count - b.getOffset(), headerBuffer, n);
  140.             write(headerBuffer, 0, n);
  141.         } else if (otp.isDeltaRepresentation()) {
  142.             int n = objectHeader(rawLength, OBJ_REF_DELTA, headerBuffer);
  143.             otp.getDeltaBaseId().copyRawTo(headerBuffer, n);
  144.             write(headerBuffer, 0, n + 20);
  145.         } else {
  146.             int n = objectHeader(rawLength, otp.getType(), headerBuffer);
  147.             write(headerBuffer, 0, n);
  148.         }
  149.     }

  150.     private static final int objectHeader(long len, int type, byte[] buf) {
  151.         byte b = (byte) ((type << 4) | (len & 0x0F));
  152.         int n = 0;
  153.         for (len >>>= 4; len != 0; len >>>= 7) {
  154.             buf[n++] = (byte) (0x80 | b);
  155.             b = (byte) (len & 0x7F);
  156.         }
  157.         buf[n++] = b;
  158.         return n;
  159.     }

  160.     private static final int ofsDelta(long diff, byte[] buf, int p) {
  161.         p += ofsDeltaVarIntLength(diff);
  162.         int n = p;
  163.         buf[--n] = (byte) (diff & 0x7F);
  164.         while ((diff >>>= 7) != 0)
  165.             buf[--n] = (byte) (0x80 | (--diff & 0x7F));
  166.         return p;
  167.     }

  168.     private static final int ofsDeltaVarIntLength(long v) {
  169.         int n = 1;
  170.         for (; (v >>>= 7) != 0; n++)
  171.             --v;
  172.         return n;
  173.     }

  174.     /**
  175.      * Get a temporary buffer writers can use to copy data with.
  176.      *
  177.      * @return a temporary buffer writers can use to copy data with.
  178.      */
  179.     public final byte[] getCopyBuffer() {
  180.         return copyBuffer;
  181.     }

  182.     void endObject() {
  183.         writeMonitor.update(1);
  184.     }

  185.     /**
  186.      * Get total number of bytes written since stream start.
  187.      *
  188.      * @return total number of bytes written since stream start.
  189.      */
  190.     public final long length() {
  191.         return count;
  192.     }

  193.     /** @return obtain the current SHA-1 digest. */
  194.     final byte[] getDigest() {
  195.         return md.digest();
  196.     }
  197. }