ProtoOutputStream.java

package de.schegge.rosinante.io;

import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.function.Function;

public class ProtoOutputStream extends FilterOutputStream {

    public ProtoOutputStream(OutputStream out) {
        super(out);
    }

    private void writeWireTypeAndFieldNumber(WireType wireType, int fieldNumber) throws IOException {
        writeVariableByteInt(fieldNumber << 3 | wireType.ordinal() & 0b00000111);
    }

    protected void writeVariableByteInt(int number) throws IOException {
        int continuationBytes = (31 - Integer.numberOfLeadingZeros(number)) / 7;
        for (int i = 0; i < continuationBytes; ++i) {
            write(((byte) ((number & 0x7F) | 0x80)));
            number >>>= 7;
        }
        write((byte) number);
    }

    protected void writeVariableByteLong(long number) throws IOException {
        int continuationBytes = (63 - Long.numberOfLeadingZeros(number)) / 7;
        for (int i = 0; i < continuationBytes; ++i) {
            write(((byte) ((number & 0x7F) | 0x80)));
            number >>>= 7;
        }
        write((byte) number);
    }

    private <T> void writeRepeatedVarInt(int fieldNumber, List<T> values, Function<T, Integer> convert) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ProtoOutputStream protoOutputStream = new ProtoOutputStream(outputStream);
        for (T value : values) {
            protoOutputStream.writeVariableByteInt(convert.apply(value));
        }
        byte[] bytes = outputStream.toByteArray();
        writeWireTypeAndFieldNumber(WireType.LEN, fieldNumber);
        writeVariableByteInt(bytes.length);
        write(bytes);
    }

    private void writeRepeatedVarLong(int fieldNumber, List<Long> values, Function<Long, Long> convert) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ProtoOutputStream protoOutputStream = new ProtoOutputStream(outputStream);
        for (Long value : values) {
            protoOutputStream.writeVariableByteLong(convert.apply(value));
        }
        byte[] bytes = outputStream.toByteArray();
        writeWireTypeAndFieldNumber(WireType.LEN, fieldNumber);
        writeVariableByteInt(bytes.length);
        write(bytes);
    }

    public void writeBoolean(int fieldNumber, boolean value) throws IOException {
        writeWireTypeAndFieldNumber(WireType.VARINT, fieldNumber);
        writeVariableByteInt(value ? 1 : 0);
    }

    public void writeInteger(int fieldNumber, int value) throws IOException {
        writeWireTypeAndFieldNumber(WireType.VARINT, fieldNumber);
        writeVariableByteInt(value);
    }

    public void writeZigZagInteger(int fieldNumber, int value) throws IOException {
        writeWireTypeAndFieldNumber(WireType.VARINT, fieldNumber);
        writeVariableByteInt((value >> 31) ^ (value << 1));
    }

    public void writeLong(int fieldNumber, long value) throws IOException {
        writeWireTypeAndFieldNumber(WireType.VARINT, fieldNumber);
        writeVariableByteLong(value);
    }

    public void writeZigZagLong(int fieldNumber, long value) throws IOException {
        writeWireTypeAndFieldNumber(WireType.VARINT, fieldNumber);
        writeVariableByteLong((value >> 63) ^ (value << 1));
    }

    public void writeString(int fieldNumber, String test) throws IOException {
        writeBytes(fieldNumber, test.getBytes(StandardCharsets.UTF_8));
    }

    public void writeBytes(int fieldNumber, byte[] bytes) throws IOException {
        writeWireTypeAndFieldNumber(WireType.LEN, fieldNumber);
        writeVariableByteInt(bytes.length);
        write(bytes);
    }

    public void writeBoolean(int fieldNumber, List<Boolean> values) throws IOException {
        writeRepeatedVarInt(fieldNumber, values, x -> x ? 1 : 0);
    }

    public void writeInteger(int fieldNumber, List<Integer> values) throws IOException {
        writeRepeatedVarInt(fieldNumber, values, x -> x);
    }

    public void writeZigZagInteger(int fieldNumber, List<Integer> values) throws IOException {
        writeRepeatedVarInt(fieldNumber, values, value -> (value >> 31) ^ (value << 1));
    }

    public void writeZigZagLong(int fieldNumber, List<Long> values) throws IOException {
        writeRepeatedVarLong(fieldNumber, values, value -> (value >> 63) ^ (value << 1));
    }

    public void writeLong(int fieldNumber, List<Long> values) throws IOException {
        writeRepeatedVarLong(fieldNumber, values, x -> x);
    }

    public void writeString(int fieldNumber, List<String> texts) throws IOException {
        for (String text : texts) {
            writeString(fieldNumber, text);
        }
    }

    public void writeBytes(int fieldNumber, List<byte[]> bytess) throws IOException {
        for (byte[] bytes : bytess) {
            writeBytes(fieldNumber, bytes);
        }
    }
}