/*
* Copyright (C) 2015 Actor LLC. <https://actor.im>
*/
package im.actor.runtime.bser;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.List;
import im.actor.runtime.collections.SparseArray;
import im.actor.runtime.collections.SparseBooleanArray;
// Disabling Bounds checks for speeding up calculations
/*-[
#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1
]-*/
public class BserWriter {
private DataOutput stream;
private SparseBooleanArray writtenFields = new SparseBooleanArray();
public BserWriter(DataOutput stream) {
if (stream == null) {
throw new IllegalArgumentException("Stream can not be null");
}
this.stream = stream;
}
public void writeBytes(int fieldNumber, @NotNull byte[] value) throws IOException {
if (value == null) {
throw new IllegalArgumentException("Value can not be null");
}
if (value.length > BserLimits.MAX_BLOCK_SIZE) {
throw new IllegalArgumentException("Unable to write more than 1 MB");
}
writtenFields.put(fieldNumber, true);
writeBytesField(fieldNumber, value);
}
public void writeString(int fieldNumber, @NotNull String value) throws IOException {
if (value == null) {
throw new IllegalArgumentException("Value can not be null");
}
writtenFields.put(fieldNumber, true);
writeBytesField(fieldNumber, value.getBytes());
}
public void writeBool(int fieldNumber, boolean value) throws IOException {
writeVarIntField(fieldNumber, value ? 1 : 0);
}
public void writeInt(int fieldNumber, int value) throws IOException {
writeVarIntField(fieldNumber, value);
}
public void writeIntFixed(int fieldNumber, int value) throws IOException {
writeVar32Fixed(fieldNumber, value);
}
public void writeDouble(int fieldNumber, double value) throws IOException {
writeVar64Fixed(fieldNumber, Double.doubleToLongBits(value));
}
public void writeLongFixed(int fieldNumber, long value) throws IOException {
writeVar64Fixed(fieldNumber, Double.doubleToLongBits(value));
}
public void writeLong(int fieldNumber, long value) throws IOException {
writeVarIntField(fieldNumber, value);
}
public void writeRepeatedLong(int fieldNumber, @NotNull List<Long> values) throws IOException {
if (values == null) {
throw new IllegalArgumentException("Values can not be null");
}
if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
throw new IllegalArgumentException("Too many values");
}
writtenFields.put(fieldNumber, true);
for (Long l : values) {
if (l == null) {
throw new IllegalArgumentException("Value can not be null");
}
writeVarIntField(fieldNumber, l);
}
}
public void writeRepeatedInt(int fieldNumber, @NotNull List<Integer> values) throws IOException {
if (values == null) {
throw new IllegalArgumentException("Values can not be null");
}
if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
throw new IllegalArgumentException("Too many values");
}
writtenFields.put(fieldNumber, true);
for (Integer l : values) {
if (l == null) {
throw new IllegalArgumentException("Value can not be null");
}
writeVarIntField(fieldNumber, l);
}
}
public void writeRepeatedBool(int fieldNumber, @NotNull List<Boolean> values) throws IOException {
if (values == null) {
throw new IllegalArgumentException("Values can not be null");
}
if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
throw new IllegalArgumentException("Too many values");
}
writtenFields.put(fieldNumber, true);
for (Boolean l : values) {
if (l == null) {
throw new IllegalArgumentException("Value can not be null");
}
writeBool(fieldNumber, l);
}
}
public void writeRepeatedString(int fieldNumber, @NotNull List<String> values) throws IOException {
if (values == null) {
throw new IllegalArgumentException("Values can not be null");
}
if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
throw new IllegalArgumentException("Too many values");
}
writtenFields.put(fieldNumber, true);
for (String l : values) {
if (l == null) {
throw new IllegalArgumentException("Value can not be null");
}
writeString(fieldNumber, l);
}
}
public void writeRepeatedBytes(int fieldNumber, @NotNull List<byte[]> values) throws IOException {
if (values == null) {
throw new IllegalArgumentException("Values can not be null");
}
if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
throw new IllegalArgumentException("Too many values");
}
writtenFields.put(fieldNumber, true);
for (byte[] l : values) {
if (l == null) {
throw new IllegalArgumentException("Value can not be null");
}
writeBytes(fieldNumber, l);
}
}
public <T extends BserObject> void writeRepeatedObj(int fieldNumber, @NotNull List<T> values) throws IOException {
if (values == null) {
throw new IllegalArgumentException("Values can not be null");
}
if (values.size() > BserLimits.MAX_PROTO_REPEATED) {
throw new IllegalArgumentException("Too many values");
}
writtenFields.put(fieldNumber, true);
for (T l : values) {
if (l == null) {
throw new IllegalArgumentException("Value can not be null");
}
writeObject(fieldNumber, l);
}
}
public void writeObject(int fieldNumber, @NotNull BserObject value) throws IOException {
if (value == null) {
throw new IllegalArgumentException("Value can not be null");
}
writtenFields.put(fieldNumber, true);
writeTag(fieldNumber, WireTypes.TYPE_LENGTH_DELIMITED);
DataOutput outputStream = new DataOutput();
BserWriter writer = new BserWriter(outputStream);
value.serialize(writer);
writeBytes(outputStream.toByteArray());
}
public void writeUnmapped(int fieldNumber, @NotNull Object value) throws IOException {
if (writtenFields.get(fieldNumber, false)) {
return;
}
if (value instanceof Long) {
writeLong(fieldNumber, (Long) value);
} else if (value instanceof byte[]) {
writeBytes(fieldNumber, (byte[]) value);
} else if (value instanceof List) {
for (Object o : (List) value) {
if (o instanceof Long) {
writeLong(fieldNumber, (Long) o);
} else if (o instanceof byte[]) {
writeBytes(fieldNumber, (byte[]) o);
} else {
throw new IOException("Incorrect unmapped value in List");
}
}
} else {
throw new IOException("Incorrect unmapped value");
}
}
public void writeRaw(byte[] raw) throws IOException {
if (raw == null) {
throw new IllegalArgumentException("Raw can not be null");
}
stream.writeBytes(raw, 0, raw.length);
}
private void writeTag(int fieldNumber, int wireType) throws IOException {
fieldNumber = (fieldNumber & 0xFFFF);
if (fieldNumber <= 0) {
throw new IllegalArgumentException("Field Number must greater than zero");
}
long tag = ((long) (fieldNumber << 3) | wireType);
stream.writeVarInt(tag);
}
private void writeVarIntField(int fieldNumber, long value) throws IOException {
writeTag(fieldNumber, WireTypes.TYPE_VARINT);
writeVarInt(value);
}
private void writeBytesField(int fieldNumber, @NotNull byte[] value) throws IOException {
writeTag(fieldNumber, WireTypes.TYPE_LENGTH_DELIMITED);
writeBytes(value);
}
private void writeVar64Fixed(int fieldNumber, long value) throws IOException {
writeTag(fieldNumber, WireTypes.TYPE_64BIT);
writeLong(value);
}
private void writeVar32Fixed(int fieldNumber, long value) throws IOException {
writeTag(fieldNumber, WireTypes.TYPE_32BIT);
writeInt(value);
}
private void writeVarInt(long value) throws IOException {
stream.writeVarInt(value & 0xFFFFFFFF);
}
private void writeLong(long v) throws IOException {
stream.writeLong(v & 0xFFFFFFFF);
}
private void writeInt(long v) throws IOException {
stream.writeInt((int) (v & 0xFFFF));
}
private void writeBytes(@NotNull byte[] data) throws IOException {
stream.writeProtoBytes(data, 0, data.length);
}
}