Skip to content

Instantly share code, notes, and snippets.

@laxika
Last active August 19, 2021 11:20
Show Gist options
  • Save laxika/b9475e042973cf69ae4f2b3524575abb to your computer and use it in GitHub Desktop.
Save laxika/b9475e042973cf69ae4f2b3524575abb to your computer and use it in GitHub Desktop.
import org.springframework.stereotype.Service;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Base64;

@Service
public class Base64Encoder {

    private static final int BUFFER_SIZE = 3 * 1024;

    public String encode(final InputStream input) throws IOException {
        try (BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE)) {
            final Base64.Encoder encoder = Base64.getEncoder();
            final StringBuilder result = new StringBuilder();

            byte[] chunk = new byte[BUFFER_SIZE];
            ByteRingBuffer byteBuffer = new ByteRingBuffer(BUFFER_SIZE * 2);
            int len;
            while ((len = in.read(chunk)) != -1) {
                byteBuffer.put(Arrays.copyOf(chunk, len));

                if (byteBuffer.available() >= BUFFER_SIZE) {
                    final byte[] workingChunk = new byte[BUFFER_SIZE];
                    byteBuffer.get(workingChunk);

                    result.append(encoder.encodeToString(workingChunk));
                }
            }

            if (byteBuffer.available() > 0) {
                final byte[] workingChunk = new byte[byteBuffer.available()];
                byteBuffer.get(workingChunk);

                result.append(encoder.encodeToString(workingChunk));
            }


            return result.toString();
        }
    }
}
public class ByteRingBuffer {

    private final byte[] buffer;
    private final int capacity;

    private int available;
    private int idxGet;
    private int idxPut;

    public ByteRingBuffer(int capacity) {
        this.capacity = capacity;
        buffer = new byte[this.capacity];
    }

    /**
     * Gets as many of the requested bytes as available from this buffer.
     *
     * @return number of bytes actually got from this buffer (0 if no bytes are available)
     */
    public int get(byte[] dst) {
        return get(dst, 0, dst.length);
    }

    /**
     * Gets as many of the requested bytes as available from this buffer.
     *
     * @return number of bytes actually got from this buffer (0 if no bytes are available)
     */
    public int get(byte[] dst, int off, int len) {
        if (available == 0) {
            return 0;
        }

        // limit is last index to read + 1
        int limit = idxGet < idxPut ? idxPut : capacity;
        int count = Math.min(limit - idxGet, len);
        System.arraycopy(buffer, idxGet, dst, off, count);
        idxGet += count;

        if (idxGet == capacity) {
            // Array end reached, check if we have more
            int count2 = Math.min(len - count, idxPut);
            if (count2 > 0) {
                System.arraycopy(buffer, 0, dst, off + count, count2);
                idxGet = count2;
                count += count2;
            } else {
                idxGet = 0;
            }
        }
        available -= count;
        return count;
    }

    /**
     * Puts as many of the given bytes as possible into this buffer.
     *
     * @return number of bytes actually put into this buffer (0 if the buffer is full)
     */
    public int put(byte[] src) {
        return put(src, 0, src.length);
    }

    /**
     * Puts as many of the given bytes as possible into this buffer.
     *
     * @return number of bytes actually put into this buffer (0 if the buffer is full)
     */
    public int put(byte[] src, int off, int len) {
        if (available == capacity) {
            return 0;
        }

        // limit is last index to put + 1
        int limit = idxPut < idxGet ? idxGet : capacity;
        int count = Math.min(limit - idxPut, len);
        System.arraycopy(src, off, buffer, idxPut, count);
        idxPut += count;

        if (idxPut == capacity) {
            // Array end reached, check if we have more
            int count2 = Math.min(len - count, idxGet);
            if (count2 > 0) {
                System.arraycopy(src, off + count, buffer, 0, count2);
                idxPut = count2;
                count += count2;
            } else {
                idxPut = 0;
            }
        }
        available += count;
        return count;
    }

    /**
     * Returns the number of bytes available and can be get without additional puts.
     */
    public int available() {
        return available;
    }
}

The ringbuffer is necessary because the BufferedInputStream doesn't always return the required bytes in the same batch (sometimes you read 1000 bytes, other times 2000 etc).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment