package org.nem.core.serialization; import org.nem.core.utils.StringEncoder; import java.io.*; import java.math.BigInteger; import java.util.*; import java.util.function.Supplier; /** * A binary deserializer that supports forward-only deserialization. */ public class BinaryDeserializer extends Deserializer implements AutoCloseable { private final ByteArrayInputStream stream; /** * Creates a new binary deserializer. * * @param bytes The byte array from which to read. * @param context The deserialization context. */ public BinaryDeserializer(final byte[] bytes, final DeserializationContext context) { super(context); this.stream = new ByteArrayInputStream(bytes); } @Override public Integer readOptionalInt(final String label) { return this.readIfNotEmpty(() -> { final byte[] bytes = this.readBytes(4); return bytes[0] & 0x000000FF | (bytes[1] << 8) & 0x0000FF00 | (bytes[2] << 16) & 0x00FF0000 | (bytes[3] << 24) & 0xFF000000; }); } @Override public Long readOptionalLong(final String label) { return this.readIfNotEmpty(() -> { final long lowPart = this.readInt(label); final long highPart = this.readInt(label); return lowPart & 0x00000000FFFFFFFFL | (highPart << 32) & 0xFFFFFFFF00000000L; }); } @Override public Double readOptionalDouble(final String label) { return this.readIfNotEmpty(() -> Double.longBitsToDouble(this.readLong(label))); } @Override public BigInteger readOptionalBigInteger(final String label) { return this.readIfNotEmpty(() -> { final byte[] bytes = this.readOptionalBytes(label); return null == bytes ? null : new BigInteger(1, bytes); }); } @Override protected byte[] readOptionalBytesImpl(final String label) { return this.readOptionalBytesUnchecked(label); } private byte[] readOptionalBytesUnchecked(final String label) { return this.readIfNotEmpty(() -> { final int numBytes = this.readInt(label); return BinarySerializer.NULL_BYTES_SENTINEL_VALUE == numBytes ? null : this.readBytes(numBytes); }); } @Override protected String readOptionalStringImpl(final String label) { return this.readIfNotEmpty(() -> { final byte[] bytes = this.readOptionalBytes(label); return null == bytes ? null : StringEncoder.getString(bytes); }); } @Override public <T> T readOptionalObject(final String label, final ObjectDeserializer<T> activator) { return this.readIfNotEmpty(() -> this.deserializeObject(label, activator)); } @Override public <T> List<T> readOptionalObjectArray(final String label, final ObjectDeserializer<T> activator) { return this.readIfNotEmpty(() -> { final int numObjects = this.readInt(label); if (BinarySerializer.NULL_BYTES_SENTINEL_VALUE == numObjects) { return null; } final List<T> objects = new ArrayList<>(); for (int i = 0; i < numObjects; ++i) { objects.add(this.deserializeObject(label, activator)); } return objects; }); } @Override public void close() throws Exception { this.stream.close(); } private <T> T deserializeObject(final String label, final ObjectDeserializer<T> activator) { try { final byte[] bytes = this.readOptionalBytesUnchecked(label); if (0 == bytes.length) { return null; } try (BinaryDeserializer deserializer = new BinaryDeserializer(bytes, this.getContext())) { return activator.deserialize(deserializer); } } catch (final Exception ex) { throw new SerializationException(ex); } } /** * Determines if there is more data left to read. * * @return true if there is more data left to read. */ public boolean hasMoreData() { return 0 != this.stream.available(); } /** * Gets the number of unread bytes in the buffer. * * @return The number of unread bytes. */ public int availableBytes() { return this.stream.available(); } private <T> T readIfNotEmpty(final Supplier<T> supplier) { return this.hasMoreData() ? supplier.get() : null; } private byte[] readBytes(final int numBytes) { if (this.stream.available() < numBytes) { throw new SerializationException("unexpected end of stream reached"); } if (0 == numBytes) { return new byte[0]; } try { final byte[] bytes = new byte[numBytes]; final int numBytesRead = this.stream.read(bytes); if (numBytesRead != numBytes) { throw new SerializationException("unexpected end of stream reached"); } return bytes; } catch (final IOException e) { throw new SerializationException(e); } } }