com.swirlds.common.io.streams.SerializableDataInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swirlds-common Show documentation
Show all versions of swirlds-common Show documentation
Swirlds is a software platform designed to build fully-distributed applications that harness the power of the cloud without servers. Now you can develop applications with fairness in decision making, speed, trust and reliability, at a fraction of the cost of traditional server-based platforms.
/*
* Copyright (C) 2016-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.swirlds.common.io.streams;
import static com.swirlds.common.io.streams.SerializableStreamConstants.NULL_CLASS_ID;
import static com.swirlds.common.io.streams.SerializableStreamConstants.NULL_LIST_ARRAY_LENGTH;
import static com.swirlds.common.io.streams.SerializableStreamConstants.NULL_VERSION;
import static com.swirlds.common.io.streams.SerializableStreamConstants.SERIALIZATION_PROTOCOL_VERSION;
import com.swirlds.base.function.CheckedFunction;
import com.swirlds.common.constructable.ConstructableRegistry;
import com.swirlds.common.io.SelfSerializable;
import com.swirlds.common.io.SerializableDet;
import com.swirlds.common.io.exceptions.ClassNotFoundException;
import com.swirlds.common.io.exceptions.InvalidVersionException;
import com.swirlds.common.utility.ValueReference;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.function.Supplier;
/**
* A drop-in replacement for {@link DataInputStream}, which handles SerializableDet classes specially. It is designed
* for use with the SerializableDet interface, and its use is described there.
*/
public class SerializableDataInputStream extends AugmentedDataInputStream {
private static final int PROTOCOL_VERSION = SERIALIZATION_PROTOCOL_VERSION;
/**
* Creates a stream capable of deserializing serializable objects.
*
* @param in the specified input stream
*/
public SerializableDataInputStream(final InputStream in) {
super(in);
}
/**
* Reads the protocol version written by {@link SerializableDataOutputStream#writeProtocolVersion()} and saves it
* internally. From this point on, it will use this version number to deserialize.
*
* @throws IOException thrown if any IO problems occur
*/
public void readProtocolVersion() throws IOException {
final int protocolVersion = readInt();
if (protocolVersion != PROTOCOL_VERSION) {
throw new IOException("invalid protocol version " + protocolVersion);
}
}
/**
* Reads a {@link SerializableDet} from a stream and returns it. The instance will be created using the
* {@link ConstructableRegistry}. The instance must have previously been written using
* {@link SerializableDataOutputStream#writeSerializable(SelfSerializable, boolean)} (SerializableDet, boolean)}
* with {@code writeClassId} set to true, otherwise we cannot know what the class written is.
*
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked to
* deserialize a class not in this set, all class IDs are permitted if null
* @param the implementation of {@link SelfSerializable} used
* @return An instance of the class previously written
* @throws IOException thrown if any IO problems occur
*/
public T readSerializable(@Nullable final Set permissibleClassIds)
throws IOException {
return readSerializable(true, SerializableDataInputStream::registryConstructor, permissibleClassIds);
}
/**
* Reads a {@link SerializableDet} from a stream and returns it. The instance will be created using the
* {@link ConstructableRegistry}. The instance must have previously been written using
* {@link SerializableDataOutputStream#writeSerializable(SelfSerializable, boolean)} (SerializableDet, boolean)}
* with {@code writeClassId} set to true, otherwise we cannot know what the class written is.
*
* @param the implementation of {@link SelfSerializable} used
* @return An instance of the class previously written
* @throws IOException thrown if any IO problems occur
*/
public T readSerializable() throws IOException {
return readSerializable(null);
}
/**
* Uses the provided {@code serializable} to read its data from the stream.
*
* @param serializableConstructor a constructor for the instance written in the stream
* @param readClassId set to true if the class ID was written to the stream
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked
* to deserialize a class not in this set, all class IDs are permitted if null.
* Ignored if readClassId is false.
* @param the implementation of {@link SelfSerializable} used
* @return the same object that was passed in, returned for convenience
* @throws IOException thrown if any IO problems occur
*/
public T readSerializable(
final boolean readClassId,
@NonNull final Supplier serializableConstructor,
@Nullable final Set permissibleClassIds)
throws IOException {
Objects.requireNonNull(serializableConstructor, "serializableConstructor must not be null");
return readSerializable(readClassId, id -> serializableConstructor.get(), permissibleClassIds);
}
/**
* Uses the provided {@code serializable} to read its data from the stream.
*
* @param serializableConstructor a constructor for the instance written in the stream
* @param readClassId set to true if the class ID was written to the stream
* @param the implementation of {@link SelfSerializable} used
* @return the same object that was passed in, returned for convenience
* @throws IOException thrown if any IO problems occur
*/
public T readSerializable(
final boolean readClassId, @NonNull final Supplier serializableConstructor) throws IOException {
return readSerializable(readClassId, serializableConstructor, null);
}
/**
* Throws an exception if the version is not supported.
*/
protected void validateVersion(final SerializableDet object, final int version) throws InvalidVersionException {
if (version < object.getMinimumSupportedVersion() || version > object.getVersion()) {
throw new InvalidVersionException(version, object);
}
}
/**
* Called when the class ID of an object becomes known. This method is a hook for the debug stream.
*
* @param classId the class ID of the current object being deserialized
*/
protected void recordClassId(final long classId) {
// debug framework can override
}
/**
* Called when the class ID of an object becomes known. This method is a hook for the debug stream.
*
* @param o the object that is being deserialized
*/
protected void recordClass(final Object o) {
// debug framework can override
}
/**
* Same as {@link #readSerializable(boolean, Supplier)} except that the constructor takes a class ID
*/
private T readSerializable(
final boolean readClassId,
@NonNull final CheckedFunction serializableConstructor,
@Nullable final Set permissibleClassIds)
throws IOException {
final Long classId;
if (readClassId) {
classId = readLong();
if (permissibleClassIds != null && !permissibleClassIds.contains(classId)) {
throw new IOException(
"Class ID " + classId + " is not in the set of permissible class IDs: " + permissibleClassIds);
}
recordClassId(classId);
if (classId == NULL_CLASS_ID) {
return null;
}
} else {
classId = null;
}
final int version = readInt();
if (version == NULL_VERSION) {
return null;
}
final T serializable = serializableConstructor.apply(classId);
recordClass(serializable);
validateVersion(serializable, version);
serializable.deserialize(this, version);
return serializable;
}
/**
* Read a sequence of serializable objects and pass them to a callback method.
*
* @param maxSize the maximum allowed size
* @param callback this method is passed each object in the sequence
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked to
* deserialize a class not in this set, all class IDs are permitted if null
* @param the type of the objects in the sequence
*/
public void readSerializableIterableWithSize(
final int maxSize, @NonNull final Consumer callback, @Nullable final Set permissibleClassIds)
throws IOException {
final int size = readInt();
checkLengthLimit(size, maxSize);
readSerializableIterableWithSizeInternal(
size, true, SerializableDataInputStream::registryConstructor, callback, permissibleClassIds);
}
/**
* Read a sequence of serializable objects and pass them to a callback method.
*
* @param maxSize the maximum allowed size
* @param callback this method is passed each object in the sequence
* @param the type of the objects in the sequence
*/
public void readSerializableIterableWithSize(
final int maxSize, @NonNull final Consumer callback) throws IOException {
readSerializableIterableWithSize(maxSize, callback, null);
}
/**
* Read a sequence of serializable objects and pass them to a callback method.
*
* @param maxSize the maximum number of objects to read
* @param readClassId if true then the class ID needs to be read
* @param serializableConstructor a method that takes a class ID and provides a constructor
* @param callback the callback method where each object is passed when it is deserialized
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked
* to deserialize a class not in this set, all class IDs are permitted if null.
* Ignored if readClassId is false.
* @param the type of the objects being deserialized
*/
public void readSerializableIterableWithSize(
final int maxSize,
final boolean readClassId,
@NonNull final Supplier serializableConstructor,
@NonNull final Consumer callback,
@Nullable final Set permissibleClassIds)
throws IOException {
final int size = readInt();
checkLengthLimit(size, maxSize);
readSerializableIterableWithSizeInternal(
size, readClassId, id -> serializableConstructor.get(), callback, permissibleClassIds);
}
/**
* Read a sequence of serializable objects and pass them to a callback method.
*
* @param maxSize the maximum number of objects to read
* @param readClassId if true then the class ID needs to be read
* @param serializableConstructor a method that takes a class ID and provides a constructor
* @param callback the callback method where each object is passed when it is deserialized
* @param the type of the objects being deserialized
*/
public void readSerializableIterableWithSize(
final int maxSize,
final boolean readClassId,
@NonNull final Supplier serializableConstructor,
@NonNull final Consumer callback)
throws IOException {
readSerializableIterableWithSize(maxSize, readClassId, serializableConstructor, callback, null);
}
/**
* Read a sequence of serializable objects and pass them to a callback method.
*
* @param size the number of objects to read
* @param readClassId if true then the class ID needs to be read
* @param serializableConstructor a method that takes a class ID and provides a constructor
* @param callback the callback method where each object is passed when it is deserialized
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked
* to deserialize a class not in this set, all class IDs are permitted if null.
* Ignored if readClassId is false.
* @param the type of the objects being deserialized
*/
private void readSerializableIterableWithSizeInternal(
final int size,
final boolean readClassId,
@NonNull final CheckedFunction serializableConstructor,
@NonNull final Consumer callback,
@Nullable final Set permissibleClassIds)
throws IOException {
if (serializableConstructor == null) {
throw new IllegalArgumentException("serializableConstructor is null");
}
// return if size is zero while deserializing similar to serializing
if (size == 0) {
return;
}
final boolean allSameClass = readBoolean();
final ValueReference classId = new ValueReference<>();
final ValueReference version = new ValueReference<>();
for (int i = 0; i < size; i++) {
final T next = readNextSerializableIteration(
allSameClass, readClassId, classId, version, serializableConstructor, permissibleClassIds);
callback.accept(next);
}
}
/**
* Helper method for {@link #readSerializableIterableWithSizeInternal(int, boolean, CheckedFunction, Consumer, Set)}
* . Protected instead of private to allow debug framework to intercept this method.
*
* @param allSameClass true if the elements all have the same class
* @param readClassId if true then the class ID needs to be read, ignored if allSameClass is true
* @param classId the class ID if known, otherwise null
* @param version the version if known, otherwise ignored
* @param serializableConstructor given a class ID, returns a constructor for that class
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked
* to deserialize a class not in this set, all class IDs are permitted if null.
* Ignored if readClassId is false.
* @param the type of the elements in the sequence
* @return true if the class ID has already been read
*/
protected T readNextSerializableIteration(
final boolean allSameClass,
final boolean readClassId,
@NonNull final ValueReference classId,
@NonNull final ValueReference version,
@NonNull final CheckedFunction serializableConstructor,
@Nullable final Set permissibleClassIds)
throws IOException {
if (!allSameClass) {
// if classes are different, we just read each object one by one
return readSerializable(readClassId, serializableConstructor, permissibleClassIds);
}
final boolean isNull = readBoolean();
if (isNull) {
return null;
}
if (version.getValue() == null) {
// this is the first non-null member, so we read the ID and version
if (readClassId) {
classId.setValue(readLong());
if (permissibleClassIds != null && !permissibleClassIds.contains(classId.getValue())) {
throw new IOException("Class ID " + classId + " is not in the set of permissible class IDs: "
+ permissibleClassIds);
}
}
version.setValue(readInt());
}
final T serializable = serializableConstructor.apply(classId.getValue());
recordClassId(serializable.getClassId());
recordClass(serializable);
serializable.deserialize(this, version.getValue());
return serializable;
}
/**
* Read a list of serializable objects from the stream
*
* @param maxListSize maximal number of object to read
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked to
* deserialize a class not in this set, all class IDs are permitted if null
* @param the implementation of {@link SelfSerializable} used
* @return A list of the instances of the class previously written
* @throws IOException thrown if any IO problems occur
*/
public List readSerializableList(
final int maxListSize, @Nullable final Set permissibleClassIds) throws IOException {
return readSerializableList(
maxListSize, true, SerializableDataInputStream::registryConstructor, permissibleClassIds);
}
/**
* Read a list of serializable objects from the stream
*
* @param maxListSize maximal number of object to read
* @param the implementation of {@link SelfSerializable} used
* @return A list of the instances of the class previously written
* @throws IOException thrown if any IO problems occur
*/
public List readSerializableList(final int maxListSize) throws IOException {
return readSerializableList(maxListSize, null);
}
/**
* Read a list of serializable objects from the stream
*
* @param maxListSize maximal number of object to read
* @param readClassId set to true if the class ID was written to the stream
* @param serializableConstructor the constructor to use when instantiating list elements
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked
* to deserialize a class not in this set, all class IDs are permitted if null.
* Ignored if readClassId is false.
* @param the implementation of {@link SelfSerializable} used
* @return A list of the instances of the class previously written
* @throws IOException thrown if any IO problems occur
*/
public List readSerializableList(
final int maxListSize,
final boolean readClassId,
@NonNull final Supplier serializableConstructor,
@Nullable final Set permissibleClassIds)
throws IOException {
Objects.requireNonNull(serializableConstructor, "serializableConstructor must not be null");
return readSerializableList(maxListSize, readClassId, id -> serializableConstructor.get(), permissibleClassIds);
}
/**
* Read a list of serializable objects from the stream
*
* @param maxListSize maximal number of object to read
* @param readClassId set to true if the class ID was written to the stream
* @param serializableConstructor the constructor to use when instantiating list elements
* @param the implementation of {@link SelfSerializable} used
* @return A list of the instances of the class previously written
* @throws IOException thrown if any IO problems occur
*/
public List readSerializableList(
final int maxListSize, final boolean readClassId, @NonNull final Supplier serializableConstructor)
throws IOException {
return readSerializableList(maxListSize, readClassId, serializableConstructor, null);
}
/**
* Read a list of serializable objects from the stream
*
* @param maxListSize maximal number of object to read
* @param readClassId set to true if the class ID was written to the stream
* @param serializableConstructor a method that takes a class ID and returns a constructor
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked
* to deserialize a class not in this set, all class IDs are permitted if null.
* Ignored if readClassId is false.
* @param the implementation of {@link SelfSerializable} used
* @return A list of the instances of the class previously written
* @throws IOException thrown if any IO problems occur
*/
private List readSerializableList(
final int maxListSize,
final boolean readClassId,
@NonNull final CheckedFunction serializableConstructor,
@Nullable final Set permissibleClassIds)
throws IOException {
final int length = readInt();
if (length == NULL_LIST_ARRAY_LENGTH) {
return null;
}
checkLengthLimit(length, maxListSize);
// ArrayList is used by default, we can add support for different list types in the future
final List list = new ArrayList<>(length);
if (length == 0) {
return list;
}
readSerializableIterableWithSizeInternal(
length, readClassId, serializableConstructor, list::add, permissibleClassIds);
return list;
}
/**
* Read an array of serializable objects from the stream.
*
* @param arrayConstructor a method that returns an array of the requested size
* @param maxListSize maximal number of object should read
* @param readClassId set to true if the class ID was written to the stream
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked to
* deserialize a class not in this set, all class IDs are permitted if null. Ignored if
* readClassId is false.
* @param the implementation of {@link SelfSerializable} used
* @return An array of the instances of the class previously written
* @throws IOException thrown if any IO problems occur
*/
public T[] readSerializableArray(
@NonNull final IntFunction arrayConstructor,
final int maxListSize,
final boolean readClassId,
@Nullable final Set permissibleClassIds)
throws IOException {
final List list = readSerializableList(
maxListSize, readClassId, SerializableDataInputStream::registryConstructor, permissibleClassIds);
if (list == null) {
return null;
}
return list.toArray(arrayConstructor.apply(list.size()));
}
/**
* Read an array of serializable objects from the stream.
*
* @param arrayConstructor a method that returns an array of the requested size
* @param maxListSize maximal number of object should read
* @param readClassId set to true if the class ID was written to the stream
* @param permissibleClassIds a set of class IDs that are allowed to be read, will throw an IOException if asked
* to deserialize a class not in this, all class IDs are permitted if null. Ignored
* if readClassId is false.
* @param serializableConstructor an object that returns new instances of the class
* @param the implementation of {@link SelfSerializable} used
* @return An array of the instances of the class previously written
* @throws IOException thrown if any IO problems occur
*/
public T[] readSerializableArray(
@NonNull final IntFunction arrayConstructor,
final int maxListSize,
final boolean readClassId,
@NonNull final Supplier serializableConstructor,
@Nullable final Set permissibleClassIds)
throws IOException {
final List list = readSerializableList(
maxListSize, readClassId, id -> serializableConstructor.get(), permissibleClassIds);
if (list == null) {
return null;
}
return list.toArray(arrayConstructor.apply(list.size()));
}
/**
* Read an array of serializable objects from the stream.
*
* @param arrayConstructor a method that returns an array of the requested size
* @param maxListSize maximal number of object we are willing to read
* @param readClassId set to true if the class ID was written to the stream
* @param serializableConstructor an object that returns new instances of the class
* @param the implementation of {@link SelfSerializable} used
* @return An array of the instances of the class previously written
* @throws IOException thrown if any IO problems occur
*/
public T[] readSerializableArray(
@NonNull final IntFunction arrayConstructor,
final int maxListSize,
final boolean readClassId,
@NonNull final Supplier serializableConstructor)
throws IOException {
return readSerializableArray(arrayConstructor, maxListSize, readClassId, serializableConstructor, null);
}
/**
* Looks up a constructor given a class ID.
*
* @param classId a requested class ID
* @param the type of the class
* @return a constructor for the class
* @throws ClassNotFoundException if the class ID is not registered
*/
private static T registryConstructor(final long classId) throws IOException {
final T rc = ConstructableRegistry.getInstance().createObject(classId);
if (rc == null) {
throw new ClassNotFoundException(classId);
}
return rc;
}
}