BinaryHunkOutputStream.java

/*
 * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0 which is available at
 * https://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
package org.eclipse.jgit.util.io;

import java.io.IOException;
import java.io.OutputStream;

import org.eclipse.jgit.util.Base85;

/**
 * An {@link OutputStream} that encodes data for a git binary patch.
 *
 * @since 5.12
 */
public class BinaryHunkOutputStream extends OutputStream {

	private static final int MAX_BYTES = 52;

	private final OutputStream out;

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

	private int pos;

	/**
	 * Creates a new {@link BinaryHunkOutputStream}.
	 *
	 * @param out
	 *            {@link OutputStream} to write the encoded data to
	 */
	public BinaryHunkOutputStream(OutputStream out) {
		this.out = out;
	}

	/**
	 * Flushes and closes this stream, and closes the underlying
	 * {@link OutputStream}.
	 */
	@Override
	public void close() throws IOException {
		flush();
		out.close();
	}

	/**
	 * Writes any buffered output as a binary patch line to the underlying
	 * {@link OutputStream} and flushes that stream, too.
	 */
	@Override
	public void flush() throws IOException {
		if (pos > 0) {
			encode(buffer, 0, pos);
			pos = 0;
		}
		out.flush();
	}

	@Override
	public void write(int b) throws IOException {
		buffer[pos++] = (byte) b;
		if (pos == buffer.length) {
			encode(buffer, 0, pos);
			pos = 0;
		}
	}

	@Override
	public void write(byte[] b, int off, int len) throws IOException {
		if (len == 0) {
			return;
		}
		int toCopy = len;
		int in = off;
		if (pos > 0) {
			// Fill the buffer
			int chunk = Math.min(toCopy, buffer.length - pos);
			System.arraycopy(b, in, buffer, pos, chunk);
			in += chunk;
			pos += chunk;
			toCopy -= chunk;
			if (pos == buffer.length) {
				encode(buffer, 0, pos);
				pos = 0;
			}
			if (toCopy == 0) {
				return;
			}
		}
		while (toCopy >= MAX_BYTES) {
			encode(b, in, MAX_BYTES);
			toCopy -= MAX_BYTES;
			in += MAX_BYTES;
		}
		if (toCopy > 0) {
			System.arraycopy(b, in, buffer, 0, toCopy);
			pos = toCopy;
		}
	}

	private void encode(byte[] data, int off, int length) throws IOException {
		if (length <= 26) {
			out.write('A' + length - 1);
		} else {
			out.write('a' + length - 27);
		}
		out.write(Base85.encode(data, off, length));
		out.write('\n');
	}
}