BinaryHunkOutputStream.java

  1. /*
  2.  * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> 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.util.io;

  11. import java.io.IOException;
  12. import java.io.OutputStream;

  13. import org.eclipse.jgit.util.Base85;

  14. /**
  15.  * An {@link OutputStream} that encodes data for a git binary patch.
  16.  *
  17.  * @since 5.12
  18.  */
  19. public class BinaryHunkOutputStream extends OutputStream {

  20.     private static final int MAX_BYTES = 52;

  21.     private final OutputStream out;

  22.     private final byte[] buffer = new byte[MAX_BYTES];

  23.     private int pos;

  24.     /**
  25.      * Creates a new {@link BinaryHunkOutputStream}.
  26.      *
  27.      * @param out
  28.      *            {@link OutputStream} to write the encoded data to
  29.      */
  30.     public BinaryHunkOutputStream(OutputStream out) {
  31.         this.out = out;
  32.     }

  33.     /**
  34.      * Flushes and closes this stream, and closes the underlying
  35.      * {@link OutputStream}.
  36.      */
  37.     @Override
  38.     public void close() throws IOException {
  39.         flush();
  40.         out.close();
  41.     }

  42.     /**
  43.      * Writes any buffered output as a binary patch line to the underlying
  44.      * {@link OutputStream} and flushes that stream, too.
  45.      */
  46.     @Override
  47.     public void flush() throws IOException {
  48.         if (pos > 0) {
  49.             encode(buffer, 0, pos);
  50.             pos = 0;
  51.         }
  52.         out.flush();
  53.     }

  54.     @Override
  55.     public void write(int b) throws IOException {
  56.         buffer[pos++] = (byte) b;
  57.         if (pos == buffer.length) {
  58.             encode(buffer, 0, pos);
  59.             pos = 0;
  60.         }
  61.     }

  62.     @Override
  63.     public void write(byte[] b, int off, int len) throws IOException {
  64.         if (len == 0) {
  65.             return;
  66.         }
  67.         int toCopy = len;
  68.         int in = off;
  69.         if (pos > 0) {
  70.             // Fill the buffer
  71.             int chunk = Math.min(toCopy, buffer.length - pos);
  72.             System.arraycopy(b, in, buffer, pos, chunk);
  73.             in += chunk;
  74.             pos += chunk;
  75.             toCopy -= chunk;
  76.             if (pos == buffer.length) {
  77.                 encode(buffer, 0, pos);
  78.                 pos = 0;
  79.             }
  80.             if (toCopy == 0) {
  81.                 return;
  82.             }
  83.         }
  84.         while (toCopy >= MAX_BYTES) {
  85.             encode(b, in, MAX_BYTES);
  86.             toCopy -= MAX_BYTES;
  87.             in += MAX_BYTES;
  88.         }
  89.         if (toCopy > 0) {
  90.             System.arraycopy(b, in, buffer, 0, toCopy);
  91.             pos = toCopy;
  92.         }
  93.     }

  94.     private void encode(byte[] data, int off, int length) throws IOException {
  95.         if (length <= 26) {
  96.             out.write('A' + length - 1);
  97.         } else {
  98.             out.write('a' + length - 27);
  99.         }
  100.         out.write(Base85.encode(data, off, length));
  101.         out.write('\n');
  102.     }
  103. }