diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/SourcesGenerator.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/SourcesGenerator.java index 8eebf67..59e6ba3 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/SourcesGenerator.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/SourcesGenerator.java @@ -33,6 +33,8 @@ import it.cavallium.data.generator.plugin.classgen.GenUpgraderBaseX; import it.cavallium.data.generator.plugin.classgen.GenUpgraderSuperX; import it.cavallium.data.generator.plugin.classgen.GenVersion; import it.cavallium.data.generator.plugin.classgen.GenVersions; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import it.unimi.dsi.fastutil.booleans.BooleanList; import it.unimi.dsi.fastutil.bytes.ByteList; import it.unimi.dsi.fastutil.chars.CharList; @@ -41,8 +43,6 @@ import it.unimi.dsi.fastutil.floats.FloatList; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.longs.LongList; import it.unimi.dsi.fastutil.shorts.ShortList; -import java.io.DataInput; -import java.io.DataOutput; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -282,10 +282,9 @@ public class SourcesGenerator { serializeMethod.addModifiers(Modifier.PUBLIC); serializeMethod.addModifiers(Modifier.FINAL); serializeMethod.returns(TypeName.VOID); - serializeMethod.addParameter(ParameterSpec.builder(DataOutput.class, "dataOutput").build()); + serializeMethod.addParameter(ParameterSpec.builder(SafeDataOutput.class, "dataOutput").build()); serializeMethod .addParameter(ParameterSpec.builder(classType, "data").addAnnotation(NotNull.class).build()); - serializeMethod.addException(IOException.class); serializeMethod.addStatement("$T.requireNonNull(data)", Objects.class); return serializeMethod; } @@ -297,8 +296,7 @@ public class SourcesGenerator { deserializeMethod.addModifiers(Modifier.PUBLIC); deserializeMethod.addModifiers(Modifier.FINAL); deserializeMethod.returns(classType); - deserializeMethod.addParameter(ParameterSpec.builder(DataInput.class, "dataInput").build()); - deserializeMethod.addException(IOException.class); + deserializeMethod.addParameter(ParameterSpec.builder(SafeDataInput.class, "dataInput").build()); return deserializeMethod; } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenCurrentVersion.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenCurrentVersion.java index 2585a50..215de7f 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenCurrentVersion.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenCurrentVersion.java @@ -14,7 +14,7 @@ import com.squareup.javapoet.WildcardTypeName; import it.cavallium.data.generator.plugin.ClassGenerator; import it.cavallium.data.generator.plugin.ComputedType; import it.cavallium.data.generator.plugin.ComputedVersion; -import java.io.DataInput; +import it.cavallium.stream.SafeDataInput; import java.io.IOException; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -119,8 +119,8 @@ public class GenCurrentVersion extends ClassGenerator { .addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC).addModifiers(Modifier.FINAL).returns(TypeVariableName.get("U")) .addParameter(ParameterSpec.builder(TypeName.INT, "oldVersion").build()).addParameter( ParameterSpec.builder(ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"), "type").build()) - .addParameter(ParameterSpec.builder(DataInput.class, "oldDataInput").build()) - .addException(IOException.class).beginControlFlow("return upgradeDataToLatestVersion(oldVersion, switch (oldVersion)"); + .addParameter(ParameterSpec.builder(SafeDataInput.class, "oldDataInput").build()) + .beginControlFlow("return upgradeDataToLatestVersion(oldVersion, switch (oldVersion)"); for (var versionConfiguration : dataModel.getVersionsSet()) { // Add a case in which the data version deserializes the serialized data and upgrades it var versions = ClassName.get(dataModel.getRootPackage(basePackageName), "Versions"); @@ -131,7 +131,7 @@ public class GenCurrentVersion extends ClassGenerator { ); } var upgradeDataToLatestVersion1Method = upgradeDataToLatestVersion1MethodBuilder - .addStatement("default -> throw new $T(\"Unknown version: \" + oldVersion)", IOException.class) + .addStatement("default -> throw new $T(\"Unknown version: \" + oldVersion)", UnsupportedOperationException.class) .addCode(CodeBlock.of("$<});")) .build(); currentVersionClass.addMethod(upgradeDataToLatestVersion1Method); @@ -145,7 +145,6 @@ public class GenCurrentVersion extends ClassGenerator { .returns(TypeVariableName.get("U")) .addParameter(ParameterSpec.builder(TypeName.INT, "oldVersion").build()) .addParameter(ParameterSpec.builder(TypeVariableName.get("T"), "oldData").build()) - .addException(IOException.class) .addStatement("$T data = oldData", Object.class); upgradeDataToLatestVersion2MethodBuilder.beginControlFlow("switch (oldVersion)"); for (var versionConfiguration : dataModel.getVersionsSet()) { @@ -168,7 +167,7 @@ public class GenCurrentVersion extends ClassGenerator { ); } } - upgradeDataToLatestVersion2MethodBuilder.addStatement("default: throw new $T(\"Unknown version: \" + oldVersion)", IOException.class); + upgradeDataToLatestVersion2MethodBuilder.addStatement("default: throw new $T(\"Unknown version: \" + oldVersion)", UnsupportedOperationException.class); upgradeDataToLatestVersion2MethodBuilder.endControlFlow(); currentVersionClass.addMethod(upgradeDataToLatestVersion2MethodBuilder.build()); } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerArrayX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerArrayX.java index eaf20a3..3d07b31 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerArrayX.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerArrayX.java @@ -14,9 +14,8 @@ import it.cavallium.data.generator.plugin.ComputedTypeArray; import it.cavallium.data.generator.plugin.ComputedTypeArrayFixed; import it.cavallium.data.generator.plugin.ComputedTypeArrayVersioned; import it.cavallium.data.generator.plugin.ComputedVersion; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import java.io.NotSerializableException; import java.util.Objects; import java.util.stream.Stream; @@ -63,9 +62,8 @@ public class GenSerializerArrayX extends ClassGenerator { var method = MethodSpec.methodBuilder("serialize"); method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); - method.addException(IOException.class); - method.addParameter(ParameterSpec.builder(DataOutput.class, "out").build()); + method.addParameter(ParameterSpec.builder(SafeDataOutput.class, "out").build()); method.addParameter(ParameterSpec .builder(typeArray.getJTypeName(basePackageName), "data") .addAnnotation(NotNull.class) @@ -96,13 +94,12 @@ public class GenSerializerArrayX extends ClassGenerator { var method = MethodSpec.methodBuilder("deserialize"); method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); - method.addException(IOException.class); var typeArrayClassName = typeArray.getJTypeName(basePackageName); method.returns(typeArrayClassName); method.addAnnotation(NotNull.class); - method.addParameter(ParameterSpec.builder(DataInput.class, "in").build()); + method.addParameter(ParameterSpec.builder(SafeDataInput.class, "in").build()); method.addStatement("int sz = in.readInt()"); var arrayTypeName = ArrayTypeName.of(typeArray.getBase().getJTypeName(basePackageName)); diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerBaseX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerBaseX.java index 67677f2..6bab686 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerBaseX.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerBaseX.java @@ -11,9 +11,8 @@ import it.cavallium.data.generator.DataSerializer; import it.cavallium.data.generator.plugin.ClassGenerator; import it.cavallium.data.generator.plugin.ComputedTypeBase; import it.cavallium.data.generator.plugin.ComputedVersion; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import java.io.NotSerializableException; import java.util.Objects; import java.util.stream.Stream; @@ -60,9 +59,8 @@ public class GenSerializerBaseX extends ClassGenerator { var method = MethodSpec.methodBuilder("serialize"); method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); - method.addException(IOException.class); - method.addParameter(ParameterSpec.builder(DataOutput.class, "out").build()); + method.addParameter(ParameterSpec.builder(SafeDataOutput.class, "out").build()); method.addParameter(ParameterSpec .builder(typeBase.getJTypeName(basePackageName), "data") .addAnnotation(NotNull.class) @@ -95,13 +93,12 @@ public class GenSerializerBaseX extends ClassGenerator { var method = MethodSpec.methodBuilder("deserialize"); method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); - method.addException(IOException.class); ClassName typeBaseClassName = typeBase.getJTypeName(basePackageName); method.returns(typeBaseClassName); method.addAnnotation(NotNull.class); - method.addParameter(ParameterSpec.builder(DataInput.class, "in").build()); + method.addParameter(ParameterSpec.builder(SafeDataInput.class, "in").build()); method.addCode("return new $T(\n$>", typeBaseClassName); typeBase.getData().entrySet().stream().flatMap(entry -> { diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerNullableX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerNullableX.java index 6111ad7..5b5857c 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerNullableX.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerNullableX.java @@ -12,9 +12,8 @@ import it.cavallium.data.generator.plugin.ComputedTypeNullable; import it.cavallium.data.generator.plugin.ComputedTypeNullableFixed; import it.cavallium.data.generator.plugin.ComputedTypeNullableVersioned; import it.cavallium.data.generator.plugin.ComputedVersion; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import java.io.NotSerializableException; import java.util.Objects; import java.util.stream.Stream; @@ -66,9 +65,8 @@ public class GenSerializerNullableX extends ClassGenerator { var baseSerializerInstance = base.getJSerializerInstance(basePackageName); method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); - method.addException(IOException.class); - method.addParameter(ParameterSpec.builder(DataOutput.class, "out").build()); + method.addParameter(ParameterSpec.builder(SafeDataOutput.class, "out").build()); method.addParameter(ParameterSpec .builder(typeNullable.getJTypeName(basePackageName), "data") .addAnnotation(NotNull.class) @@ -101,13 +99,12 @@ public class GenSerializerNullableX extends ClassGenerator { var baseSerializerInstance = base.getJSerializerInstance(basePackageName); method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); - method.addException(IOException.class); var typeNullableClassName = typeNullable.getJTypeName(basePackageName); method.returns(typeNullableClassName); method.addAnnotation(NotNull.class); - method.addParameter(ParameterSpec.builder(DataInput.class, "in").build()); + method.addParameter(ParameterSpec.builder(SafeDataInput.class, "in").build()); method.addStatement("return in.readBoolean() ? new $T(($T) $T.$N.deserialize(in)) : $T.empty()", typeNullableClassName, diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerSuperX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerSuperX.java index 6e57868..4c3b43d 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerSuperX.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerSuperX.java @@ -11,8 +11,8 @@ import it.cavallium.data.generator.plugin.ClassGenerator; import it.cavallium.data.generator.plugin.ComputedType; import it.cavallium.data.generator.plugin.ComputedTypeSuper; import it.cavallium.data.generator.plugin.ComputedVersion; -import java.io.DataInput; -import java.io.DataOutput; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import java.io.IOException; import java.io.NotSerializableException; import java.util.Objects; @@ -61,11 +61,10 @@ public class GenSerializerSuperX extends ClassGenerator { int max = typeSuper.subTypes().size(); var method = MethodSpec.methodBuilder("checkIdValidity"); method.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); - method.addException(IOException.class); method.addParameter(ParameterSpec.builder(int.class, "id").build()); method.beginControlFlow("if (id < 0 || id >= $L)", max); - method.addStatement("throw new $T(new $T(id))", IOException.class, IndexOutOfBoundsException.class); + method.addStatement("throw new new $T(id)", IndexOutOfBoundsException.class); method.endControlFlow(); classBuilder.addMethod(method.build()); @@ -75,9 +74,8 @@ public class GenSerializerSuperX extends ClassGenerator { var method = MethodSpec.methodBuilder("serialize"); method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); - method.addException(IOException.class); - method.addParameter(ParameterSpec.builder(DataOutput.class, "out").build()); + method.addParameter(ParameterSpec.builder(SafeDataOutput.class, "out").build()); method.addParameter(ParameterSpec .builder(typeSuper.getJTypeName(basePackageName), "data") .addAnnotation(NotNull.class) @@ -118,13 +116,12 @@ public class GenSerializerSuperX extends ClassGenerator { var method = MethodSpec.methodBuilder("deserialize"); method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); - method.addException(IOException.class); ClassName typeSuperClassName = typeSuper.getJTypeName(basePackageName); method.returns(typeSuperClassName); method.addAnnotation(NotNull.class); - method.addParameter(ParameterSpec.builder(DataInput.class, "in").build()); + method.addParameter(ParameterSpec.builder(SafeDataInput.class, "in").build()); method.addStatement("int id = in.readUnsignedByte()"); method.beginControlFlow("return switch (id)"); diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderBaseX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderBaseX.java index 2354b4c..0d85641 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderBaseX.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderBaseX.java @@ -79,7 +79,6 @@ public class GenUpgraderBaseX extends ClassGenerator { var method = MethodSpec.methodBuilder("upgrade"); method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); - method.addException(IOException.class); ClassName typeBaseClassName = typeBase.getJTypeName(basePackageName); ClassName nextTypeBaseClassName = nextTypeBase.getJTypeName(basePackageName); diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderSuperX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderSuperX.java index 0008ffd..0dd4225 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderSuperX.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderSuperX.java @@ -13,7 +13,6 @@ import it.cavallium.data.generator.plugin.ComputedType; import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; import it.cavallium.data.generator.plugin.ComputedTypeSuper; import it.cavallium.data.generator.plugin.ComputedVersion; -import java.io.IOException; import java.util.stream.Stream; import javax.lang.model.element.Modifier; import org.jetbrains.annotations.NotNull; @@ -61,7 +60,6 @@ public class GenUpgraderSuperX extends ClassGenerator { var method = MethodSpec.methodBuilder("upgrade"); method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); - method.addException(IOException.class); ClassName typeSuperClassName = typeSuper.getJTypeName(basePackageName); ClassName nextTypeSuperClassName = nextTypeSuper.getJTypeName(basePackageName); diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersion.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersion.java index e127a18..1dc1dd3 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersion.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersion.java @@ -19,7 +19,6 @@ import it.cavallium.data.generator.plugin.ComputedTypeCustom; import it.cavallium.data.generator.plugin.ComputedTypeNullableFixed; import it.cavallium.data.generator.plugin.ComputedTypeSuper; import it.cavallium.data.generator.plugin.ComputedVersion; -import java.io.IOException; import java.util.Objects; import java.util.stream.Stream; import javax.lang.model.element.Modifier; @@ -105,7 +104,6 @@ public class GenVersion extends ClassGenerator { var methodBuilder = MethodSpec.methodBuilder("upgradeToNextVersion"); methodBuilder.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); - methodBuilder.addException(ClassName.get(IOException.class)); var nextIBaseType = ClassName.get(nextVersion.getPackage(basePackageName), "IBaseType"); methodBuilder.returns(nextIBaseType); diff --git a/data-generator-runtime/src/main/java/it/cavallium/buffer/Buf.java b/data-generator-runtime/src/main/java/it/cavallium/buffer/Buf.java new file mode 100644 index 0000000..dc56e3c --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/buffer/Buf.java @@ -0,0 +1,186 @@ +package it.cavallium.buffer; + +import it.cavallium.stream.SafeByteArrayInputStream; +import it.cavallium.stream.SafeByteArrayOutputStream; +import it.cavallium.stream.SafeDataOutput; +import it.unimi.dsi.fastutil.bytes.ByteArrayList; +import it.unimi.dsi.fastutil.bytes.ByteList; +import java.nio.charset.Charset; +import java.util.RandomAccess; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface Buf extends ByteList, RandomAccess { + static Buf wrap(ByteList bytes) { + if (bytes instanceof Buf buf) { + return buf; + } else if (bytes instanceof ByteArrayList byteArrayList) { + return ByteListBuf.wrap(byteArrayList.elements(), byteArrayList.size()); + } else { + return ByteListBuf.wrap(bytes.toByteArray()); + } + } + static Buf wrap(ByteList bytes, int from, int to) { + if (bytes instanceof Buf buf) { + return buf.subList(from, to); + } else if (bytes instanceof ByteArrayList byteArrayList) { + return ByteListBuf.wrap(byteArrayList.elements(), byteArrayList.size()).subList(from, to); + } else { + return ByteListBuf.wrap(bytes.toByteArray()).subList(from, to); + } + } + + static Buf wrap(byte[] bytes) { + return ByteListBuf.wrap(bytes); + } + + static Buf wrap(byte[] bytes, int from, int to) { + return ByteListBuf.wrap(bytes, to).subList(from, to); + } + + static Buf create(int initialCapacity) { + return new ByteListBuf(initialCapacity); + } + + static Buf copyOf(byte[] original) { + return new ByteListBuf(original); + } + + static Buf create() { + return new ByteListBuf(); + } + + static Buf wrap(byte[] array, int length) { + return ByteListBuf.wrap(array, length); + } + + static Buf createZeroes(int length) { + return ByteListBuf.wrap(new byte[length], length); + } + + /** + * Get this element as an array, converting it if needed + */ + byte @NotNull[] asArray(); + + /** + * Get this element as an array, only if it's already an array, otherwise return null + */ + byte @Nullable[] asArrayStrict(); + + /** + * Get this element as an array with equal or bigger size, converting it if needed + * The returned array may be bigger than expected! + */ + byte @Nullable[] asUnboundedArray(); + + /** + * Get this element as an array with equal or bigger size, only if it's already an array, otherwise return null + * The returned array may be bigger than expected! + */ + byte @Nullable[] asUnboundedArrayStrict(); + + boolean isMutable(); + + void freeze(); + + @Override + Buf subList(int from, int to); + + Buf copy(); + + SafeByteArrayInputStream binaryInputStream(); + + void writeTo(SafeDataOutput dataOutput); + + default long getLong(int i) { + byte b1 = getByte(i); + byte b2 = getByte(i + 1); + byte b3 = getByte(i + 2); + byte b4 = getByte(i + 3); + byte b5 = getByte(i + 4); + byte b6 = getByte(i + 5); + byte b7 = getByte(i + 6); + byte b8 = getByte(i + 7); + return (b1 & 0xFFL) << 56 + | (b2 & 0xFFL) << 48 + | (b3 & 0xFFL) << 40 + | (b4 & 0xFFL) << 32 + | (b5 & 0xFFL) << 24 + | (b6 & 0xFFL) << 16 + | (b7 & 0xFFL) << 8 + | (b8 & 0xFFL); + } + + default int getInt(int i) { + byte b1 = getByte(i); + byte b2 = getByte(i + 1); + byte b3 = getByte(i + 2); + byte b4 = getByte(i + 3); + return b1 << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF); + } + + default float getFloat(int i) { + return Float.intBitsToFloat(getInt(i)); + } + + default double getDouble(int i) { + return Double.longBitsToDouble(getLong(i)); + } + + default boolean getBoolean(int i) { + return getByte(i) != 0; + } + + default void setBoolean(int i, boolean val) { + set(i, val ? (byte) 1 : 0); + } + + default void setByte(int i, byte val) { + set(i, val); + } + + default void setInt(int i, int val) { + set(i, (byte) (val >> 24)); + set(i + 1, (byte) (val >> 16)); + set(i + 2, (byte) (val >> 8)); + set(i + 3, (byte) val); + } + + default void setLong(int i, long val) { + set(i, (byte) (val >> 56)); + set(i + 1, (byte) (val >> 48)); + set(i + 2, (byte) (val >> 40)); + set(i + 3, (byte) (val >> 32)); + set(i + 4, (byte) (val >> 24)); + set(i + 5, (byte) (val >> 16)); + set(i + 6, (byte) (val >> 8)); + set(i + 7, (byte) val); + } + + default void setFloat(int i, float val) { + setInt(i, Float.floatToRawIntBits(val)); + } + + default void setDouble(int i, double val) { + setLong(i, Double.doubleToRawLongBits(val)); + } + + default SafeByteArrayOutputStream binaryOutputStream() { + return binaryOutputStream(0, size()); + } + + default SafeByteArrayOutputStream binaryOutputStream(int from) { + return binaryOutputStream(from, size()); + } + + SafeByteArrayOutputStream binaryOutputStream(int from, int to); + + boolean equals(int aStartIndex, Buf b, int bStartIndex, int length); + + boolean equals(int aStartIndex, byte[] b, int bStartIndex, int length); + + default String toString(Charset charset) { + return new String(this.asArray(), charset); + } +} diff --git a/data-generator-runtime/src/main/java/it/cavallium/buffer/BufDataInput.java b/data-generator-runtime/src/main/java/it/cavallium/buffer/BufDataInput.java new file mode 100644 index 0000000..0e80ede --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/buffer/BufDataInput.java @@ -0,0 +1,42 @@ +package it.cavallium.buffer; + +import it.cavallium.stream.SafeByteArrayInputStream; +import it.cavallium.stream.SafeDataInputStream; +import org.jetbrains.annotations.NotNull; + + +public class BufDataInput extends SafeDataInputStream { + + /** + * Creates a DataInputStream that uses the specified underlying InputStream. + * + * @param in the specified input stream + */ + private BufDataInput(@NotNull SafeByteArrayInputStream in) { + super(in); + } + + public static BufDataInput create(Buf byteList) { + return new BufDataInput(byteList.binaryInputStream()); + } + + @Deprecated + @Override + public void close() { + } + + @Override + public void mark(int readlimit) { + throw new UnsupportedOperationException(); + } + + @Override + public void reset() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean markSupported() { + return false; + } +} diff --git a/data-generator-runtime/src/main/java/it/cavallium/buffer/BufDataOutput.java b/data-generator-runtime/src/main/java/it/cavallium/buffer/BufDataOutput.java new file mode 100644 index 0000000..4edf654 --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/buffer/BufDataOutput.java @@ -0,0 +1,247 @@ +package it.cavallium.buffer; + +import static it.cavallium.stream.SafeDataOutputStream.strLen; +import static it.cavallium.stream.SafeDataOutputStream.utfLen; + +import it.cavallium.stream.SafeByteArrayOutputStream; +import it.cavallium.stream.SafeDataOutputStream; +import it.unimi.dsi.fastutil.Arrays; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +public class BufDataOutput implements DataOutput { + + private final SafeByteArrayOutputStream buf; + private final SafeDataOutputStream dOut; + private final int limit; + + private BufDataOutput(SafeByteArrayOutputStream buf) { + this.buf = buf; + this.dOut = new SafeDataOutputStream(buf); + limit = Integer.MAX_VALUE; + } + + private BufDataOutput(SafeByteArrayOutputStream buf, int maxSize) { + this.buf = buf; + this.dOut = new SafeDataOutputStream(buf); + this.limit = maxSize; + } + + public static BufDataOutput createLimited(int maxSize, int hint) { + if (hint >= 0) { + if (maxSize < 0 || maxSize == Integer.MAX_VALUE) { + return create(hint); + } else { + return new BufDataOutput(new SafeByteArrayOutputStream(Math.min(maxSize, hint)), maxSize); + } + } else { + return createLimited(maxSize); + } + } + + public static BufDataOutput createLimited(int maxSize) { + if (maxSize < 0 || maxSize == Integer.MAX_VALUE) { + return create(); + } else { + return new BufDataOutput(new SafeByteArrayOutputStream(maxSize), maxSize); + } + } + + public static BufDataOutput create() { + return new BufDataOutput(new SafeByteArrayOutputStream()); + } + + public static BufDataOutput create(int hint) { + if (hint >= 0) { + return new BufDataOutput(new SafeByteArrayOutputStream(hint)); + } else { + return create(); + } + } + + public static BufDataOutput wrap(Buf buf, int from, int to) { + Arrays.ensureFromTo(buf.size(), from, to); + if (buf.isEmpty()) { + return createLimited(0); + } else { + return new BufDataOutput(buf.binaryOutputStream(from), to - from); + } + } + + public static BufDataOutput wrap(Buf buf) { + if (buf.isEmpty()) { + return createLimited(0); + } else { + return new BufDataOutput(buf.binaryOutputStream(), buf.size()); + } + } + + private IllegalStateException unreachable(IOException ex) { + return new IllegalStateException(ex); + } + + @Override + public void write(int b) { + // Fast inlined checkOutOfBounds + if (dOut.size() >= limit) { + throw new IndexOutOfBoundsException(limit); + } + + dOut.write(b); + } + + private void checkOutOfBounds(int delta) { + if (dOut.size() + delta > limit) { + throw new IndexOutOfBoundsException(limit); + } + } + + @Override + public void write(byte @NotNull [] b) { + checkOutOfBounds(b.length); + dOut.write(b); + } + + @Override + public void write(byte @NotNull [] b, int off, int len) { + checkOutOfBounds(Math.max(0, Math.min(b.length - off, len))); + dOut.write(b, off, len); + } + + @Override + public void writeBoolean(boolean v) { + // Fast inlined checkOutOfBounds + if (dOut.size() >= limit) { + throw new IndexOutOfBoundsException(limit); + } + + dOut.writeBoolean(v); + } + + @Override + public void writeByte(int v) { + this.write(v); + } + + @Override + public void writeShort(int v) { + checkOutOfBounds(Short.BYTES); + dOut.writeShort(v); + } + + @Override + public void writeChar(int v) { + checkOutOfBounds(Character.BYTES); + dOut.writeChar(v); + } + + @Override + public void writeInt(int v) { + checkOutOfBounds(Integer.BYTES); + dOut.writeInt(v); + } + + @Override + public void writeLong(long v) { + checkOutOfBounds(Long.BYTES); + dOut.writeLong(v); + } + + public void writeInt52(long v) { + checkOutOfBounds(7); + dOut.writeInt52(v); + } + + @Override + public void writeFloat(float v) { + checkOutOfBounds(Float.BYTES); + dOut.writeFloat(v); + } + + @Override + public void writeDouble(double v) { + checkOutOfBounds(Double.BYTES); + dOut.writeDouble(v); + } + + public void ensureWritable(int size) { + dOut.flush(); + buf.ensureWritable(size); + } + + @Override + public void writeBytes(@NotNull String s) { + checkOutOfBounds(s.length() * Byte.BYTES); + dOut.writeBytes(s); + } + + // todo: check + public void writeBytes(Buf deserialized) { + checkOutOfBounds(deserialized.size()); + deserialized.writeTo(dOut); + } + + public void writeBytes(byte[] b, int off, int len) { + write(b, off, len); + } + + @Override + public void writeChars(@NotNull String s) { + checkOutOfBounds(Character.BYTES * s.length()); + dOut.writeChars(s); + } + + private static String tooLongMsg(String s, int bits32) { + int slen = s.length(); + String head = s.substring(0, 8); + String tail = s.substring(slen - 8, slen); + // handle int overflow with max 3x expansion + long actualLength = (long)slen + Integer.toUnsignedLong(bits32 - slen); + return "encoded string (" + head + "..." + tail + ") too long: " + + actualLength + " bytes"; + } + + @Override + public void writeUTF(@NotNull String str) { + var strlen = strLen(str); + var utflen = utfLen(str, strlen); + var bytes = Short.BYTES + utflen; + checkOutOfBounds(bytes); + dOut.writeUTF(strlen, utflen, str); + } + + public Buf asList() { + dOut.flush(); + return Buf.wrap(this.buf.array, this.buf.length); + } + + @Override + public String toString() { + return dOut.toString(); + } + + @Override + public int hashCode() { + return dOut.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BufDataOutput that = (BufDataOutput) o; + + return Objects.equals(dOut, that.dOut); + } + + public int size() { + return dOut.size(); + } +} diff --git a/data-generator-runtime/src/main/java/it/cavallium/buffer/ByteListBuf.java b/data-generator-runtime/src/main/java/it/cavallium/buffer/ByteListBuf.java new file mode 100644 index 0000000..954b2bd --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/buffer/ByteListBuf.java @@ -0,0 +1,467 @@ +package it.cavallium.buffer; + +import it.cavallium.stream.SafeByteArrayInputStream; +import it.cavallium.stream.SafeByteArrayOutputStream; +import it.cavallium.stream.SafeDataOutput; +import it.unimi.dsi.fastutil.bytes.AbstractByteList; +import it.unimi.dsi.fastutil.bytes.ByteArrayList; +import it.unimi.dsi.fastutil.bytes.ByteCollection; +import it.unimi.dsi.fastutil.bytes.ByteConsumer; +import it.unimi.dsi.fastutil.bytes.ByteIterator; +import it.unimi.dsi.fastutil.bytes.ByteIterators; +import it.unimi.dsi.fastutil.bytes.ByteList; +import it.unimi.dsi.fastutil.bytes.ByteListIterator; +import it.unimi.dsi.fastutil.bytes.ByteSpliterator; +import it.unimi.dsi.fastutil.bytes.ByteSpliterators; +import java.io.Serial; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class ByteListBuf extends ByteArrayList implements Buf { + + private boolean mutable = true; + + protected ByteListBuf(byte[] a, boolean wrapped) { + super(a, wrapped); + } + + public ByteListBuf(int capacity) { + super(capacity); + } + + public ByteListBuf() { + } + + public ByteListBuf(Collection extends Byte> c) { + super(c); + } + + public ByteListBuf(ByteCollection c) { + super(c); + } + + public ByteListBuf(ByteList l) { + super(l); + } + + public ByteListBuf(byte[] a) { + super(a); + } + + public ByteListBuf(byte[] a, int offset, int length) { + super(a, offset, length); + } + + public ByteListBuf(Iterator extends Byte> i) { + super(i); + } + + public ByteListBuf(ByteIterator i) { + super(i); + } + + /** + * Wraps a given array into an array list of given size. + * + *
+ * Note it is guaranteed that the type of the array returned by {@link #elements()} will be the same + * (see the comments in the class documentation). + * + * @param a an array to wrap. + * @param length the length of the resulting array list. + * @return a new array list of the given size, wrapping the given array. + */ + public static ByteListBuf wrap(final byte[] a, final int length) { + if (length > a.length) throw new IllegalArgumentException("The specified length (" + length + ") is greater than the array size (" + a.length + ")"); + final ByteListBuf l = new ByteListBuf(a, true); + l.size = length; + return l; + } + + /** + * Wraps a given array into an array list. + * + *
+ * Note it is guaranteed that the type of the array returned by {@link #elements()} will be the same
+ * (see the comments in the class documentation).
+ *
+ * @param a an array to wrap.
+ * @return a new array list wrapping the given array.
+ */
+ public static ByteListBuf wrap(final byte[] a) {
+ return wrap(a, a.length);
+ }
+
+ /**
+ * Creates a new empty array list.
+ *
+ * @return a new empty array list.
+ */
+ public static ByteListBuf of() {
+ return new ByteListBuf();
+ }
+
+ /**
+ * Creates an array list using an array of elements.
+ *
+ * @param init a the array the will become the new backing array of the array list.
+ * @return a new array list backed by the given array.
+ * @see #wrap
+ */
+
+ public static ByteListBuf of(final byte... init) {
+ return wrap(init);
+ }
+
+ @Override
+ public byte @NotNull [] asArray() {
+ if (this.size() == a.length) {
+ return this.a;
+ } else {
+ return this.toByteArray();
+ }
+ }
+
+ @Override
+ public byte @Nullable [] asArrayStrict() {
+ if (this.size() == a.length) {
+ return a;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public byte @Nullable [] asUnboundedArray() {
+ return a;
+ }
+
+ @Override
+ public byte @Nullable [] asUnboundedArrayStrict() {
+ return a;
+ }
+
+ @Override
+ public boolean isMutable() {
+ return mutable;
+ }
+
+ @Override
+ public void freeze() {
+ mutable = false;
+ }
+
+ @Override
+ public Buf subList(int from, int to) {
+ if (from == 0 && to == size()) return this;
+ ensureIndex(from);
+ ensureIndex(to);
+ if (from > to) throw new IndexOutOfBoundsException("Start index (" + from + ") is greater than end index (" + to + ")");
+ return new SubList(from, to);
+ }
+
+ @Override
+ public Buf copy() {
+ var copied = ByteListBuf.wrap(this.a.clone());
+ copied.size = this.size;
+ return copied;
+ }
+
+ @Override
+ public SafeByteArrayInputStream binaryInputStream() {
+ return new SafeByteArrayInputStream(this.a, 0, this.size);
+ }
+
+ @Override
+ public void writeTo(SafeDataOutput dataOutput) {
+ dataOutput.write(this.a, 0, this.size);
+ }
+
+ @Override
+ public SafeByteArrayOutputStream binaryOutputStream(int from, int to) {
+ it.unimi.dsi.fastutil.Arrays.ensureFromTo(size, from, to);
+ return new SafeByteArrayOutputStream(a, from, to);
+ }
+
+ @Override
+ public boolean equals(int aStartIndex, Buf b, int bStartIndex, int length) {
+ return b.equals(bStartIndex, this.a, aStartIndex, length);
+ }
+
+ @Override
+ public boolean equals(int aStartIndex, byte[] b, int bStartIndex, int length) {
+ if (aStartIndex < 0) return false;
+ if (aStartIndex + length > this.size) {
+ return false;
+ }
+ return Arrays.equals(a, aStartIndex, aStartIndex + length, b, bStartIndex, bStartIndex + length);
+ }
+
+ @Override
+ public String toString(Charset charset) {
+ return new String(a, 0, size, charset);
+ }
+
+ private class SubList extends AbstractByteList.ByteRandomAccessSubList implements Buf {
+ @Serial
+ private static final long serialVersionUID = -3185226345314976296L;
+
+ private boolean subMutable = true;
+
+ protected SubList(int from, int to) {
+ super(ByteListBuf.this, from, to);
+ }
+
+ // Most of the inherited methods should be fine, but we can override a few of them for performance.
+ // Needed because we can't access the parent class' instance variables directly in a different
+ // instance of SubList.
+ private byte[] getParentArray() {
+ return a;
+ }
+
+ @Override
+ public @NotNull Buf subList(int from, int to) {
+ it.unimi.dsi.fastutil.Arrays.ensureFromTo(a.length, from, to);
+ if (from > to) throw new IllegalArgumentException("Start index (" + from + ") is greater than end index (" + to + ")");
+ // Sadly we have to rewrap this, because if there is a sublist of a sublist, and the
+ // subsublist adds, both sublists need to update their "to" value.
+ return new SubList(from, to);
+ }
+
+ @Override
+ public Buf copy() {
+ return Buf.wrap(Arrays.copyOfRange(a, from, to));
+ }
+
+ @Override
+ public SafeByteArrayInputStream binaryInputStream() {
+ return new SafeByteArrayInputStream(a, from, size());
+ }
+
+ @Override
+ public void writeTo(SafeDataOutput dataOutput) {
+ dataOutput.write(a, from, size());
+ }
+
+ @Override
+ public SafeByteArrayOutputStream binaryOutputStream(int from, int to) {
+ it.unimi.dsi.fastutil.Arrays.ensureFromTo(size(), from, to);
+ return new SafeByteArrayOutputStream(a, from + this.from, to + this.from);
+ }
+
+ @Override
+ public boolean equals(int aStartIndex, Buf b, int bStartIndex, int length) {
+ return b.equals(bStartIndex, a, aStartIndex + from, length);
+ }
+
+ @Override
+ public boolean equals(int aStartIndex, byte[] b, int bStartIndex, int length) {
+ var aFrom = from + aStartIndex;
+ var aTo = from + aStartIndex + length;
+ if (aFrom < from) return false;
+ if (aTo > to) return false;
+ return Arrays.equals(a, aFrom, aTo, b, bStartIndex, bStartIndex + length);
+ }
+
+ @Override
+ public byte getByte(int i) {
+ ensureRestrictedIndex(i);
+ return a[i + from];
+ }
+
+ @Override
+ public byte @NotNull [] asArray() {
+ if (this.from == 0 && this.to == a.length) {
+ return a;
+ } else {
+ return toByteArray();
+ }
+ }
+
+ @Override
+ public byte @Nullable [] asArrayStrict() {
+ if (this.from == 0 && this.to == a.length) {
+ return a;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public byte @Nullable [] asUnboundedArray() {
+ if (from == 0) {
+ return a;
+ } else {
+ return toByteArray();
+ }
+ }
+
+ @Override
+ public byte @Nullable [] asUnboundedArrayStrict() {
+ if (from == 0) {
+ return a;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isMutable() {
+ return mutable && subMutable;
+ }
+
+ @Override
+ public void freeze() {
+ subMutable = false;
+ }
+
+ private final class SubListIterator extends ByteIterators.AbstractIndexBasedListIterator {
+ // We are using pos == 0 to be 0 relative to SubList.from (meaning you need to do a[from + i] when
+ // accessing array).
+ SubListIterator(int index) {
+ super(0, index);
+ }
+
+ @Override
+ protected byte get(int i) {
+ return a[from + i];
+ }
+
+ @Override
+ protected void add(int i, byte k) {
+ ByteListBuf.SubList.this.add(i, k);
+ }
+
+ @Override
+ protected void set(int i, byte k) {
+ ByteListBuf.SubList.this.set(i, k);
+ }
+
+ @Override
+ protected void remove(int i) {
+ ByteListBuf.SubList.this.removeByte(i);
+ }
+
+ @Override
+ protected int getMaxPos() {
+ return to - from;
+ }
+
+ @Override
+ public byte nextByte() {
+ if (!hasNext()) throw new NoSuchElementException();
+ return a[from + (lastReturned = pos++)];
+ }
+
+ @Override
+ public byte previousByte() {
+ if (!hasPrevious()) throw new NoSuchElementException();
+ return a[from + (lastReturned = --pos)];
+ }
+
+ @Override
+ public void forEachRemaining(final ByteConsumer action) {
+ final int max = to - from;
+ while (pos < max) {
+ action.accept(a[from + (lastReturned = pos++)]);
+ }
+ }
+ }
+
+ @Override
+ public @NotNull ByteListIterator listIterator(int index) {
+ return new ByteListBuf.SubList.SubListIterator(index);
+ }
+
+ private final class SubListSpliterator extends ByteSpliterators.LateBindingSizeIndexBasedSpliterator {
+ // We are using pos == 0 to be 0 relative to real array 0
+ SubListSpliterator() {
+ super(from);
+ }
+
+ private SubListSpliterator(int pos, int maxPos) {
+ super(pos, maxPos);
+ }
+
+ @Override
+ protected int getMaxPosFromBackingStore() {
+ return to;
+ }
+
+ @Override
+ protected byte get(int i) {
+ return a[i];
+ }
+
+ @Override
+ protected ByteListBuf.SubList.SubListSpliterator makeForSplit(int pos, int maxPos) {
+ return new ByteListBuf.SubList.SubListSpliterator(pos, maxPos);
+ }
+
+ @Override
+ public boolean tryAdvance(final ByteConsumer action) {
+ if (pos >= getMaxPos()) return false;
+ action.accept(a[pos++]);
+ return true;
+ }
+
+ @Override
+ public void forEachRemaining(final ByteConsumer action) {
+ final int max = getMaxPos();
+ while (pos < max) {
+ action.accept(a[pos++]);
+ }
+ }
+ }
+
+ @Override
+ public ByteSpliterator spliterator() {
+ return new ByteListBuf.SubList.SubListSpliterator();
+ }
+
+ boolean contentsEquals(byte[] otherA, int otherAFrom, int otherATo) {
+ if (a == otherA && from == otherAFrom && to == otherATo) return true;
+ return Arrays.equals(a, from, to, otherA, otherAFrom, otherATo);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (o == null) return false;
+ if (!(o instanceof java.util.List)) return false;
+ if (o instanceof ByteListBuf other) {
+ return contentsEquals(other.a, 0, other.size());
+ }
+ if (o instanceof SubList other) {
+ return contentsEquals(other.getParentArray(), other.from, other.to);
+ }
+ return super.equals(o);
+ }
+
+ int contentsCompareTo(byte[] otherA, int otherAFrom, int otherATo) {
+ if (a == otherA && from == otherAFrom && to == otherATo) return 0;
+ return Arrays.compareUnsigned(a, from, to, otherA, otherAFrom, otherATo);
+ }
+
+ @Override
+ public int compareTo(final java.util.@NotNull List extends Byte> l) {
+ if (l instanceof ByteListBuf other) {
+ return contentsCompareTo(other.a, 0, other.size());
+ }
+ if (l instanceof ByteListBuf.SubList other) {
+ return contentsCompareTo(other.getParentArray(), other.from, other.to);
+ }
+ return super.compareTo(l);
+ }
+
+ @Override
+ public String toString(Charset charset) {
+ return new String(a, from, to, charset);
+ }
+ }
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/DataSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/DataSerializer.java
index dd363fa..b929dab 100644
--- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/DataSerializer.java
+++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/DataSerializer.java
@@ -1,14 +1,12 @@
package it.cavallium.data.generator;
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
+import it.cavallium.stream.SafeDataInput;
+import it.cavallium.stream.SafeDataOutput;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
public interface DataSerializer Warning: this class implements the correct semantics
+ * of {@link #read(byte[], int, int)} as described in {@link java.io.InputStream}.
+ * The implementation given in {@link java.io.ByteArrayInputStream} is broken,
+ * but it will never be fixed because it's too late.
+ *
+ * @author Sebastiano Vigna
+ */
+
+public class SafeByteArrayInputStream extends SafeMeasurableInputStream implements SafeRepositionableStream {
+
+ /** The array backing the input stream. */
+ public byte[] array;
+
+ /** The first valid entry. */
+ public int offset;
+
+ /** The number of valid bytes in {@link #array} starting from {@link #offset}. */
+ public int length;
+
+ /** The current position as a distance from {@link #offset}. */
+ private int position;
+
+ /** The current mark as a position, or -1 if no mark exists. */
+ private int mark;
+
+ /** Creates a new array input stream using a given array fragment.
+ *
+ * @param array the backing array.
+ * @param offset the first valid entry of the array.
+ * @param length the number of valid bytes.
+ */
+ public SafeByteArrayInputStream(final byte[] array, final int offset, final int length) {
+ this.array = array;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ /** Creates a new array input stream using a given array.
+ *
+ * @param array the backing array.
+ */
+ public SafeByteArrayInputStream(final byte[] array) {
+ this(array, 0, array.length);
+ }
+
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+ @Override
+ public void reset() {
+ position = mark;
+ }
+
+ /** Closing a fast byte array input stream has no effect. */
+ @Override
+ public void close() {}
+
+ @Override
+ public void mark(final int dummy) {
+ mark = position;
+ }
+
+ @Override
+ public int available() {
+ return length - position;
+ }
+
+ @Override
+ public long skip(long n) {
+ if (n <= length - position) {
+ position += (int)n;
+ return n;
+ }
+ n = length - position;
+ position = length;
+ return n;
+ }
+
+ @Override
+ public int read() {
+ if (length == position) return -1;
+ return array[offset + position++] & 0xFF;
+ }
+
+ /** Reads bytes from this byte-array input stream as
+ * specified in {@link java.io.InputStream#read(byte[], int, int)}.
+ * Note that the implementation given in {@link java.io.ByteArrayInputStream#read(byte[], int, int)}
+ * will return -1 on a zero-length read at EOF, contrarily to the specification. We won't.
+ */
+
+ @Override
+ public int read(final byte b[], final int offset, final int length) {
+ if (this.length == this.position) return length == 0 ? 0 : -1;
+ final int n = Math.min(length, this.length - this.position);
+ System.arraycopy(array, this.offset + this.position, b, offset, n);
+ this.position += n;
+ return n;
+ }
+
+ @Override
+ public long position() {
+ return position;
+ }
+
+ @Override
+ public void position(final long newPosition) {
+ position = (int)Math.min(newPosition, length);
+ }
+
+ @Override
+ public long length() {
+ return length;
+ }
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeByteArrayOutputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeByteArrayOutputStream.java
new file mode 100644
index 0000000..0e5464a
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeByteArrayOutputStream.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2005-2022 Sebastiano Vigna
+ *
+ * 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 it.cavallium.stream;
+
+import it.unimi.dsi.fastutil.Arrays;
+import it.unimi.dsi.fastutil.bytes.ByteArrays;
+
+/** Simple, fast byte-array output stream that exposes the backing array.
+ *
+ * {@link java.io.ByteArrayOutputStream} is nice, but to get its content you
+ * must generate each time a new object. This doesn't happen here.
+ *
+ * This class will automatically enlarge the backing array, doubling its
+ * size whenever new space is needed. The {@link #reset()} method will
+ * mark the content as empty, but will not decrease the capacity: use
+ * {@link #trim()} for that purpose.
+ *
+ * @author Sebastiano Vigna
+ */
+
+public class SafeByteArrayOutputStream extends SafeMeasurableOutputStream implements SafeRepositionableStream {
+
+ /** The array backing the output stream. */
+ public static final int DEFAULT_INITIAL_CAPACITY = 16;
+ private final boolean wrapped;
+ private final int initialPosition;
+ private final int initialLength;
+
+ /** The array backing the output stream. */
+ public byte[] array;
+
+ /** The number of valid bytes in {@link #array}. */
+ public int length;
+
+ /** The current writing position. */
+ private int position;
+
+ /** Creates a new array output stream with an initial capacity of {@link #DEFAULT_INITIAL_CAPACITY} bytes. */
+ public SafeByteArrayOutputStream() {
+ this(DEFAULT_INITIAL_CAPACITY);
+ }
+
+ /** Creates a new array output stream with a given initial capacity.
+ *
+ * @param initialCapacity the initial length of the backing array.
+ */
+ public SafeByteArrayOutputStream(final int initialCapacity) {
+ array = new byte[initialCapacity];
+ wrapped = false;
+ initialPosition = 0;
+ initialLength = 0;
+ }
+
+ /** Creates a new array output stream wrapping a given byte array.
+ *
+ * @param a the byte array to wrap.
+ */
+ public SafeByteArrayOutputStream(final byte[] a) {
+ array = a;
+ wrapped = true;
+ initialPosition = 0;
+ initialLength = a.length;
+ }
+
+ /** Creates a new array output stream wrapping a given byte array.
+ *
+ * @param a the byte array to wrap.
+ */
+ public SafeByteArrayOutputStream(final byte[] a, int from, int to) {
+ Arrays.ensureFromTo(a.length, from, to);
+ wrapped = true;
+ array = a;
+ initialPosition = from;
+ initialLength = to;
+ position = from;
+ length = to - from;
+ }
+
+ /** Marks this array output stream as empty. */
+ public void reset() {
+ length = initialLength;
+ position = initialPosition;
+ }
+
+ /** Ensures that the length of the backing array is equal to {@link #length}. */
+ public void trim() {
+ if (!wrapped) {
+ array = ByteArrays.trim(array, length);
+ }
+ }
+
+ public void ensureWritable(int size) {
+ growBy(size);
+ }
+
+ @Override
+ public void write(final int b) {
+ if (position >= array.length) {
+ if (wrapped) {
+ throw new ArrayIndexOutOfBoundsException(position);
+ } else {
+ array = ByteArrays.grow(array, position + 1, length);
+ }
+ }
+ array[position++] = (byte)b;
+ if (length < position) length = position;
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len) {
+ ByteArrays.ensureOffsetLength(b, off, len);
+ growBy(len);
+ System.arraycopy(b, off, array, position, len);
+ if (position + len > length) length = position += len;
+ }
+
+ private void growBy(int len) {
+ if (position + len > array.length) {
+ if (wrapped) {
+ throw new ArrayIndexOutOfBoundsException(position + len - 1);
+ } else {
+ array = ByteArrays.grow(array, position + len, position);
+ }
+ }
+ }
+
+ @Override
+ public void position(final long newPosition) {
+ position = (int)newPosition;
+ }
+
+ @Override
+ public long position() {
+ return position;
+ }
+
+ @Override
+ public long length() {
+ return length;
+ }
+
+ /**
+ * This method copies the array
+ */
+ public byte[] toByteArray() {
+ return java.util.Arrays.copyOf(array, length);
+ }
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataInput.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataInput.java
new file mode 100644
index 0000000..7619e88
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataInput.java
@@ -0,0 +1,146 @@
+package it.cavallium.stream;
+
+import java.io.Closeable;
+import java.io.DataInput;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A data input stream lets an application read primitive Java data
+ * types from an underlying input stream in a machine-independent
+ * way. An application uses a data output stream to write data that
+ * can later be read by a data input stream.
+ *
+ * DataInputStream is not necessarily safe for multithreaded access.
+ * Thread safety is optional and is the responsibility of users of
+ * methods in this class.
+ *
+ * @author Arthur van Hoff
+ * @see java.io.DataOutputStream
+ * @since 1.0
+ */
+public interface SafeDataInput extends Closeable, DataInput {
+
+ /**
+ * Reads some number of bytes from the contained input stream and
+ * stores them into the buffer array {@code b}. The number of
+ * bytes actually read is returned as an integer. This method blocks
+ * until input data is available, end of file is detected, or an
+ * exception is thrown.
+ *
+ * If {@code b} is null, a {@code NullPointerException} is
+ * thrown. If the length of {@code b} is zero, then no bytes are
+ * read and {@code 0} is returned; otherwise, there is an attempt
+ * to read at least one byte. If no byte is available because the
+ * stream is at end of file, the value {@code -1} is returned;
+ * otherwise, at least one byte is read and stored into {@code b}.
+ *
+ * The first byte read is stored into element {@code b[0]}, the
+ * next one into {@code b[1]}, and so on. The number of bytes read
+ * is, at most, equal to the length of {@code b}. Let {@code k}
+ * be the number of bytes actually read; these bytes will be stored in
+ * elements {@code b[0]} through {@code b[k-1]}, leaving
+ * elements {@code b[k]} through {@code b[b.length-1]}
+ * unaffected.
+ *
+ * The {@code read(b)} method has the same effect as:
+ * This method blocks until input data is available, end of file is
+ * detected, or an exception is thrown.
+ *
+ * If {@code len} is zero, then no bytes are read and
+ * {@code 0} is returned; otherwise, there is an attempt to read at
+ * least one byte. If no byte is available because the stream is at end of
+ * file, the value {@code -1} is returned; otherwise, at least one
+ * byte is read and stored into {@code b}.
+ *
+ * The first byte read is stored into element {@code b[off]}, the
+ * next one into {@code b[off+1]}, and so on. The number of bytes read
+ * is, at most, equal to {@code len}. Let k be the number of
+ * bytes actually read; these bytes will be stored in elements
+ * {@code b[off]} through {@code b[off+}k{@code -1]},
+ * leaving elements {@code b[off+}k{@code ]} through
+ * {@code b[off+len-1]} unaffected.
+ *
+ * In every case, elements {@code b[0]} through
+ * {@code b[off]} and elements {@code b[off+len]} through
+ * {@code b[b.length-1]} are unaffected.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in the destination array {@code b}
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * {@code -1} if there is no more data because the end
+ * of the stream has been reached.
+ * @throws NullPointerException If {@code b} is {@code null}.
+ * @throws IndexOutOfBoundsException If {@code off} is negative,
+ * {@code len} is negative, or {@code len} is greater than
+ * {@code b.length - off}
+ * @see SafeFilterInputStream#in
+ * @see java.io.InputStream#read(byte[], int, int)
+ */
+ int read(byte[] b, int off, int len);
+
+ void readFully(byte @NotNull [] b);
+
+ void readFully(byte @NotNull [] b, int off, int len);
+
+ int skipBytes(int n);
+
+ boolean readBoolean();
+
+ byte readByte();
+
+ int readUnsignedByte();
+
+ short readShort();
+
+ int readUnsignedShort();
+
+ char readChar();
+
+ int readInt();
+
+ long readLong();
+
+ /**
+ * See the general contract of the {@code readInt52}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next seven bytes of this input stream, interpreted as a
+ * {@code Int52}.
+ * @see SafeFilterInputStream#in
+ */
+ long readInt52();
+
+ float readFloat();
+
+ double readDouble();
+
+ @Deprecated
+ String readLine();
+
+ @NotNull String readUTF();
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataInputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataInputStream.java
new file mode 100644
index 0000000..95fc6b9
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataInputStream.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+package it.cavallium.stream;
+
+import org.jetbrains.annotations.NotNull;
+
+public class SafeDataInputStream extends SafeFilterInputStream implements SafeDataInput {
+
+ /**
+ * Creates a DataInputStream that uses the specified
+ * underlying InputStream.
+ *
+ * @param in the specified input stream
+ */
+ public SafeDataInputStream(SafeInputStream in) {
+ super(in);
+ }
+
+ /**
+ * working arrays initialized on demand by readUTF
+ */
+ private byte[] bytearr = new byte[80];
+ private char[] chararr = new char[80];
+
+ @Override
+ public final int read(byte[] b) {
+ return in.read(b, 0, b.length);
+ }
+
+ @Override
+ public final int read(byte[] b, int off, int len) {
+ return in.read(b, off, len);
+ }
+
+ /**
+ * See the general contract of the {@code readFully}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @param b the buffer into which the data is read.
+ * @throws NullPointerException if {@code b} is {@code null}.
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ public final void readFully(byte @NotNull [] b) {
+ readFully(b, 0, b.length);
+ }
+
+ /**
+ * See the general contract of the {@code readFully}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in the data array {@code b}.
+ * @param len the number of bytes to read.
+ * @throws NullPointerException if {@code b} is {@code null}.
+ * @throws IndexOutOfBoundsException if {@code off} is negative,
+ * {@code len} is negative, or {@code len} is greater than
+ * {@code b.length - off}.
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ public final void readFully(byte @NotNull [] b, int off, int len) {
+ if (len < 0)
+ throw new IndexOutOfBoundsException();
+ int n = 0;
+ while (n < len) {
+ int count = in.read(b, off + n, len - n);
+ if (count < 0)
+ throw new IndexOutOfBoundsException();
+ n += count;
+ }
+ }
+
+ /**
+ * See the general contract of the {@code skipBytes}
+ * method of {@code DataInput}.
+ *
+ * Bytes for this operation are read from the contained
+ * input stream.
+ *
+ * @param n the number of bytes to be skipped.
+ * @return the actual number of bytes skipped.
+ */
+ @Override
+ public final int skipBytes(int n) {
+ int total = 0;
+ int cur;
+
+ while ((total
+ * Bytes for this operation are read from the contained
+ * input stream.
+ *
+ * @return the {@code boolean} value read.
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ public final boolean readBoolean() {
+ int ch = in.read();
+ if (ch < 0)
+ throw new IndexOutOfBoundsException();
+ return (ch != 0);
+ }
+
+ /**
+ * See the general contract of the {@code readByte}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next byte of this input stream as a signed 8-bit
+ * {@code byte}.
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ public final byte readByte() {
+ int ch = in.read();
+ if (ch < 0)
+ throw new IndexOutOfBoundsException();
+ return (byte)(ch);
+ }
+
+ /**
+ * See the general contract of the {@code readUnsignedByte}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next byte of this input stream, interpreted as an
+ * unsigned 8-bit number.
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ public final int readUnsignedByte() {
+ int ch = in.read();
+ if (ch < 0)
+ throw new IndexOutOfBoundsException();
+ return ch;
+ }
+
+ /**
+ * See the general contract of the {@code readShort}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next two bytes of this input stream, interpreted as a
+ * signed 16-bit number.
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ public final short readShort() {
+ int ch1 = in.read();
+ int ch2 = in.read();
+ if ((ch1 | ch2) < 0)
+ throw new IndexOutOfBoundsException();
+ return (short)((ch1 << 8) + (ch2));
+ }
+
+ /**
+ * See the general contract of the {@code readUnsignedShort}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next two bytes of this input stream, interpreted as an
+ * unsigned 16-bit integer.
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ public final int readUnsignedShort() {
+ int ch1 = in.read();
+ int ch2 = in.read();
+ if ((ch1 | ch2) < 0)
+ throw new IndexOutOfBoundsException();
+ return (ch1 << 8) + (ch2);
+ }
+
+ /**
+ * See the general contract of the {@code readChar}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next two bytes of this input stream, interpreted as a
+ * {@code char}.
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ public final char readChar() {
+ int ch1 = in.read();
+ int ch2 = in.read();
+ if ((ch1 | ch2) < 0)
+ throw new IndexOutOfBoundsException();
+ return (char)((ch1 << 8) + (ch2));
+ }
+
+ /**
+ * See the general contract of the {@code readInt}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next four bytes of this input stream, interpreted as an
+ * {@code int}.
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ public final int readInt() {
+ int ch1 = in.read();
+ int ch2 = in.read();
+ int ch3 = in.read();
+ int ch4 = in.read();
+ if ((ch1 | ch2 | ch3 | ch4) < 0)
+ throw new IndexOutOfBoundsException();
+ return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4));
+ }
+
+ private final byte[] readBuffer = new byte[8];
+
+ /**
+ * See the general contract of the {@code readLong}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next eight bytes of this input stream, interpreted as a
+ * {@code long}.
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ public final long readLong() {
+ readFully(readBuffer, 0, 8);
+ return (((long)readBuffer[0] << 56) +
+ ((long)(readBuffer[1] & 255) << 48) +
+ ((long)(readBuffer[2] & 255) << 40) +
+ ((long)(readBuffer[3] & 255) << 32) +
+ ((long)(readBuffer[4] & 255) << 24) +
+ ((readBuffer[5] & 255) << 16) +
+ ((readBuffer[6] & 255) << 8) +
+ ((readBuffer[7] & 255)));
+ }
+
+ @Override
+ public final long readInt52() {
+ readFully(readBuffer, 0, 7);
+ return ((long) (readBuffer[0] & 0xf) << 48)
+ + ((long) (readBuffer[1] & 0xff) << 40)
+ + ((long) (readBuffer[2] & 0xff) << 32)
+ + ((long) (readBuffer[3] & 0xff) << 24)
+ + ((long) (readBuffer[4] & 0xff) << 16)
+ + ((long) (readBuffer[5] & 0xff) << 8)
+ + (long) (readBuffer[6] & 0xff);
+ }
+
+ /**
+ * See the general contract of the {@code readFloat}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next four bytes of this input stream, interpreted as a
+ * {@code float}.
+ * @see SafeDataInputStream#readInt()
+ * @see Float#intBitsToFloat(int)
+ */
+ @Override
+ public final float readFloat() {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ /**
+ * See the general contract of the {@code readDouble}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next eight bytes of this input stream, interpreted as a
+ * {@code double}.
+ * @see SafeDataInputStream#readLong()
+ * @see Double#longBitsToDouble(long)
+ */
+ @Override
+ public final double readDouble() {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ private char[] lineBuffer;
+
+ /**
+ * See the general contract of the {@code readLine}
+ * method of {@code DataInput}.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @deprecated This method does not properly convert bytes to characters.
+ * As of JDK 1.1, the preferred way to read lines of text is via the
+ * {@code BufferedReader.readLine()} method. Programs that use the
+ * {@code DataInputStream} class to read lines can be converted to use
+ * the {@code BufferedReader} class by replacing code of the form:
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return a Unicode string.
+ * @see SafeDataInputStream#readUTF(SafeDataInputStream)
+ */
+ @Override
+ public final @NotNull String readUTF() {
+ return readUTF(this);
+ }
+
+ /**
+ * Reads from the
+ * stream {@code in} a representation
+ * of a Unicode character string encoded in
+ * modified UTF-8 format;
+ * this string of characters is then returned as a {@code String}.
+ * The details of the modified UTF-8 representation
+ * are exactly the same as for the {@code readUTF}
+ * method of {@code DataInput}.
+ *
+ * @param in a data input stream.
+ * @return a Unicode string.
+ * @see SafeDataInputStream#readUnsignedShort()
+ */
+ public static String readUTF(SafeDataInputStream in) {
+ int utflen = in.readUnsignedShort();
+ byte[] bytearr;
+ char[] chararr;
+ if (in.bytearr.length < utflen){
+ in.bytearr = new byte[utflen*2];
+ in.chararr = new char[utflen*2];
+ }
+ chararr = in.chararr;
+ bytearr = in.bytearr;
+
+ int c, char2, char3;
+ int count = 0;
+ int chararr_count=0;
+
+ in.readFully(bytearr, 0, utflen);
+
+ while (count < utflen) {
+ c = (int) bytearr[count] & 0xff;
+ if (c > 127) break;
+ count++;
+ chararr[chararr_count++]=(char)c;
+ }
+
+ while (count < utflen) {
+ c = (int) bytearr[count] & 0xff;
+ switch (c >> 4) {
+ case 0, 1, 2, 3, 4, 5, 6, 7 -> {
+ /* 0xxxxxxx*/
+ count++;
+ chararr[chararr_count++] = (char) c;
+ }
+ case 12, 13 -> {
+ /* 110x xxxx 10xx xxxx*/
+ count += 2;
+ if (count > utflen)
+ throw new IllegalArgumentException("malformed input: partial character at end");
+ char2 = bytearr[count - 1];
+ if ((char2 & 0xC0) != 0x80)
+ throw new IllegalArgumentException("malformed input around byte " + count);
+ chararr[chararr_count++] = (char) (((c & 0x1F) << 6) | (char2 & 0x3F));
+ }
+ case 14 -> {
+ /* 1110 xxxx 10xx xxxx 10xx xxxx */
+ count += 3;
+ if (count > utflen)
+ throw new IllegalArgumentException("malformed input: partial character at end");
+ char2 = bytearr[count - 2];
+ char3 = bytearr[count - 1];
+ if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
+ throw new IllegalArgumentException("malformed input around byte " + (count - 1));
+ chararr[chararr_count++] = (char) (((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F)));
+ }
+ default ->
+ /* 10xx xxxx, 1111 xxxx */
+ throw new IllegalArgumentException("malformed input around byte " + count);
+ }
+ }
+ // The number of chars produced may be less than utflen
+ return new String(chararr, 0, chararr_count);
+ }
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataOutput.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataOutput.java
new file mode 100644
index 0000000..870f9cd
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataOutput.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 1995, 2020, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+package it.cavallium.stream;
+
+/**
+ * The {@code SafeDataOutput} interface provides
+ * for converting data from any of the Java
+ * primitive types to a series of bytes and
+ * writing these bytes to a binary stream.
+ * There is also a facility for converting
+ * a {@code String} into
+ * modified UTF-8
+ * format and writing the resulting series
+ * of bytes.
+ *
+ * For all the methods in this interface that
+ * write bytes, it is generally true that if
+ * a byte cannot be written for any reason,
+ * an {@code IOException} is thrown.
+ *
+ * @author Frank Yellin
+ * @see java.io.DataInput
+ * @see java.io.DataOutputStream
+ * @since 1.0
+ */
+public interface SafeDataOutput {
+ /**
+ * Writes to the output stream the eight
+ * low-order bits of the argument {@code b}.
+ * The 24 high-order bits of {@code b}
+ * are ignored.
+ *
+ * @param b the byte to be written.
+ */
+ void write(int b);
+
+ /**
+ * Writes to the output stream all the bytes in array {@code b}.
+ * If {@code b} is {@code null},
+ * a {@code NullPointerException} is thrown.
+ * If {@code b.length} is zero, then
+ * no bytes are written. Otherwise, the byte
+ * {@code b[0]} is written first, then
+ * {@code b[1]}, and so on; the last byte
+ * written is {@code b[b.length-1]}.
+ *
+ * @param b the data.
+ */
+ void write(byte b[]);
+
+ /**
+ * Writes {@code len} bytes from array
+ * {@code b}, in order, to
+ * the output stream. If {@code b}
+ * is {@code null}, a {@code NullPointerException}
+ * is thrown. If {@code off} is negative,
+ * or {@code len} is negative, or {@code off+len}
+ * is greater than the length of the array
+ * {@code b}, then an {@code IndexOutOfBoundsException}
+ * is thrown. If {@code len} is zero,
+ * then no bytes are written. Otherwise, the
+ * byte {@code b[off]} is written first,
+ * then {@code b[off+1]}, and so on; the
+ * last byte written is {@code b[off+len-1]}.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ */
+ void write(byte b[], int off, int len);
+
+ /**
+ * Writes a {@code boolean} value to this output stream.
+ * If the argument {@code v}
+ * is {@code true}, the value {@code (byte)1}
+ * is written; if {@code v} is {@code false},
+ * the value {@code (byte)0} is written.
+ * The byte written by this method may
+ * be read by the {@code readBoolean}
+ * method of interface {@code DataInput},
+ * which will then return a {@code boolean}
+ * equal to {@code v}.
+ *
+ * @param v the boolean to be written.
+ */
+ void writeBoolean(boolean v);
+
+ /**
+ * Writes to the output stream the eight low-order
+ * bits of the argument {@code v}.
+ * The 24 high-order bits of {@code v}
+ * are ignored. (This means that {@code writeByte}
+ * does exactly the same thing as {@code write}
+ * for an integer argument.) The byte written
+ * by this method may be read by the {@code readByte}
+ * method of interface {@code DataInput},
+ * which will then return a {@code byte}
+ * equal to {@code (byte)v}.
+ *
+ * @param v the byte value to be written.
+ */
+ void writeByte(int v);
+
+ /**
+ * Writes two bytes to the output
+ * stream to represent the value of the argument.
+ * The byte values to be written, in the order
+ * shown, are:
+ *
+ * The bytes written by this method may be
+ * read by the {@code readShort} method
+ * of interface {@code DataInput}, which
+ * will then return a {@code short} equal
+ * to {@code (short)v}.
+ *
+ * @param v the {@code short} value to be written.
+ */
+ void writeShort(int v);
+
+ /**
+ * Writes a {@code char} value, which
+ * is comprised of two bytes, to the
+ * output stream.
+ * The byte values to be written, in the order
+ * shown, are:
+ *
+ * The bytes written by this method may be
+ * read by the {@code readChar} method
+ * of interface {@code DataInput}, which
+ * will then return a {@code char} equal
+ * to {@code (char)v}.
+ *
+ * @param v the {@code char} value to be written.
+ */
+ void writeChar(int v);
+
+ /**
+ * Writes an {@code int} value, which is
+ * comprised of four bytes, to the output stream.
+ * The byte values to be written, in the order
+ * shown, are:
+ *
+ * The bytes written by this method may be read
+ * by the {@code readInt} method of interface
+ * {@code DataInput}, which will then
+ * return an {@code int} equal to {@code v}.
+ *
+ * @param v the {@code int} value to be written.
+ */
+ void writeInt(int v);
+
+ /**
+ * Writes a {@code long} value, which is
+ * comprised of eight bytes, to the output stream.
+ * The byte values to be written, in the order
+ * shown, are:
+ *
+ * The bytes written by this method may be
+ * read by the {@code readLong} method
+ * of interface {@code DataInput}, which
+ * will then return a {@code long} equal
+ * to {@code v}.
+ *
+ * @param v the {@code long} value to be written.
+ */
+ void writeLong(long v);
+
+ /**
+ * Writes a {@code Int52} value, which is
+ * comprised of seven bytes, to the output stream.
+ * The byte values to be written, in the order
+ * shown, are:
+ *
+ * The bytes written by this method may be
+ * read by the {@code readInt52} method
+ * of interface {@code DataInput}, which
+ * will then return a {@code Int52} equal
+ * to {@code v}.
+ *
+ * @param v the {@code Int52} value to be written.
+ */
+ void writeInt52(long v);
+
+ /**
+ * Writes a {@code float} value,
+ * which is comprised of four bytes, to the output stream.
+ * It does this as if it first converts this
+ * {@code float} value to an {@code int}
+ * in exactly the manner of the {@code Float.floatToIntBits}
+ * method and then writes the {@code int}
+ * value in exactly the manner of the {@code writeInt}
+ * method. The bytes written by this method
+ * may be read by the {@code readFloat}
+ * method of interface {@code DataInput},
+ * which will then return a {@code float}
+ * equal to {@code v}.
+ *
+ * @param v the {@code float} value to be written.
+ */
+ void writeFloat(float v);
+
+ /**
+ * Writes a {@code double} value,
+ * which is comprised of eight bytes, to the output stream.
+ * It does this as if it first converts this
+ * {@code double} value to a {@code long}
+ * in exactly the manner of the {@code Double.doubleToLongBits}
+ * method and then writes the {@code long}
+ * value in exactly the manner of the {@code writeLong}
+ * method. The bytes written by this method
+ * may be read by the {@code readDouble}
+ * method of interface {@code DataInput},
+ * which will then return a {@code double}
+ * equal to {@code v}.
+ *
+ * @param v the {@code double} value to be written.
+ */
+ void writeDouble(double v);
+
+ /**
+ * Writes a string to the output stream.
+ * For every character in the string
+ * {@code s}, taken in order, one byte
+ * is written to the output stream. If
+ * {@code s} is {@code null}, a {@code NullPointerException}
+ * is thrown. If {@code s.length}
+ * is zero, then no bytes are written. Otherwise,
+ * the character {@code s[0]} is written
+ * first, then {@code s[1]}, and so on;
+ * the last character written is {@code s[s.length-1]}.
+ * For each character, one byte is written,
+ * the low-order byte, in exactly the manner
+ * of the {@code writeByte} method . The
+ * high-order eight bits of each character
+ * in the string are ignored.
+ *
+ * @param s the string of bytes to be written.
+ */
+ void writeBytes(String s);
+
+ /**
+ * Writes every character in the string {@code s},
+ * to the output stream, in order,
+ * two bytes per character. If {@code s}
+ * is {@code null}, a {@code NullPointerException}
+ * is thrown. If {@code s.length}
+ * is zero, then no characters are written.
+ * Otherwise, the character {@code s[0]}
+ * is written first, then {@code s[1]},
+ * and so on; the last character written is
+ * {@code s[s.length-1]}. For each character,
+ * two bytes are actually written, high-order
+ * byte first, in exactly the manner of the
+ * {@code writeChar} method.
+ *
+ * @param s the string value to be written.
+ */
+ void writeChars(String s);
+
+ /**
+ * Writes two bytes of length information
+ * to the output stream, followed
+ * by the
+ * modified UTF-8
+ * representation
+ * of every character in the string {@code s}.
+ * If {@code s} is {@code null},
+ * a {@code NullPointerException} is thrown.
+ * Each character in the string {@code s}
+ * is converted to a group of one, two, or
+ * three bytes, depending on the value of the
+ * character.
+ * If a character {@code c}
+ * is in the range
+ * If a character {@code c} is If a character
+ * {@code c} is in the range First,
+ * the total number of bytes needed to represent
+ * all the characters of {@code s} is
+ * calculated. If this number is larger than
+ * {@code 65535}, then a {@code UTFDataFormatException}
+ * is thrown. Otherwise, this length is written
+ * to the output stream in exactly the manner
+ * of the {@code writeShort} method;
+ * after this, the one-, two-, or three-byte
+ * representation of each character in the
+ * string {@code s} is written. The
+ * bytes written by this method may be read
+ * by the {@code readUTF} method of interface
+ * {@code DataInput}, which will then
+ * return a {@code String} equal to {@code s}.
+ *
+ * @param s the string value to be written.
+ */
+ void writeUTF(String s);
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataOutputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataOutputStream.java
new file mode 100644
index 0000000..03c366d
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeDataOutputStream.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+package it.cavallium.stream;
+
+import java.io.DataOutputStream;
+
+/**
+ * A data output stream lets an application write primitive Java data
+ * types to an output stream in a portable way. An application can
+ * then use a data input stream to read the data back in.
+ *
+ * @author unascribed
+ * @see java.io.DataInputStream
+ * @since 1.0
+ */
+public class SafeDataOutputStream extends SafeFilterOutputStream implements SafeDataOutput {
+ /**
+ * The number of bytes written to the data output stream so far.
+ * If this counter overflows, it will be wrapped to Integer.MAX_VALUE.
+ */
+ protected int written;
+
+ /**
+ * bytearr is initialized on demand by writeUTF
+ */
+ private byte[] bytearr = null;
+
+ /**
+ * Creates a new data output stream to write data to the specified
+ * underlying output stream. The counter {@code written} is
+ * set to zero.
+ *
+ * @param out the underlying output stream, to be saved for later
+ * use.
+ * @see SafeFilterOutputStream#out
+ */
+ public SafeDataOutputStream(SafeOutputStream out) {
+ super(out);
+ }
+
+ /**
+ * Increases the written counter by the specified value
+ * until it reaches Integer.MAX_VALUE.
+ */
+ private void incCount(int value) {
+ int temp = written + value;
+ if (temp < 0) {
+ temp = Integer.MAX_VALUE;
+ }
+ written = temp;
+ }
+
+ /**
+ * Writes the specified byte (the low eight bits of the argument
+ * {@code b}) to the underlying output stream. If no exception
+ * is thrown, the counter {@code written} is incremented by
+ * {@code 1}.
+ *
+ * Implements the {@code write} method of {@code OutputStream}.
+ *
+ * @param b the {@code byte} to be written.
+ * @see SafeFilterOutputStream#out
+ */
+ public void write(int b) {
+ out.write(b);
+ incCount(1);
+ }
+
+ /**
+ * Writes {@code len} bytes from the specified byte array
+ * starting at offset {@code off} to the underlying output stream.
+ * If no exception is thrown, the counter {@code written} is
+ * incremented by {@code len}.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ * @see SafeFilterOutputStream#out
+ */
+ public void write(byte[] b, int off, int len)
+ {
+ out.write(b, off, len);
+ incCount(len);
+ }
+
+ /**
+ * Flushes this data output stream. This forces any buffered output
+ * bytes to be written out to the stream.
+ *
+ * The {@code flush} method of {@code SafeDataOutputStream}
+ * calls the {@code flush} method of its underlying output stream.
+ *
+ * @see SafeFilterOutputStream#out
+ * @see java.io.OutputStream#flush()
+ */
+ public void flush() {
+ out.flush();
+ }
+
+ /**
+ * Writes a {@code boolean} to the underlying output stream as
+ * a 1-byte value. The value {@code true} is written out as the
+ * value {@code (byte)1}; the value {@code false} is
+ * written out as the value {@code (byte)0}. If no exception is
+ * thrown, the counter {@code written} is incremented by
+ * {@code 1}.
+ *
+ * @param v a {@code boolean} value to be written.
+ * @see SafeFilterOutputStream#out
+ */
+ public final void writeBoolean(boolean v) {
+ out.write(v ? 1 : 0);
+ incCount(1);
+ }
+
+ /**
+ * Writes out a {@code byte} to the underlying output stream as
+ * a 1-byte value. If no exception is thrown, the counter
+ * {@code written} is incremented by {@code 1}.
+ *
+ * @param v a {@code byte} value to be written.
+ * @see SafeFilterOutputStream#out
+ */
+ public final void writeByte(int v) {
+ this.write(v);
+ }
+
+ /**
+ * Writes a {@code short} to the underlying output stream as two
+ * bytes, high byte first. If no exception is thrown, the counter
+ * {@code written} is incremented by {@code 2}.
+ *
+ * @param v a {@code short} to be written.
+ * @see SafeFilterOutputStream#out
+ */
+ public final void writeShort(int v) {
+ out.write((v >>> 8) & 0xFF);
+ out.write((v) & 0xFF);
+ incCount(2);
+ }
+
+ /**
+ * Writes a {@code char} to the underlying output stream as a
+ * 2-byte value, high byte first. If no exception is thrown, the
+ * counter {@code written} is incremented by {@code 2}.
+ *
+ * @param v a {@code char} value to be written.
+ * @see SafeFilterOutputStream#out
+ */
+ public final void writeChar(int v) {
+ out.write((v >>> 8) & 0xFF);
+ out.write((v) & 0xFF);
+ incCount(2);
+ }
+
+ /**
+ * Writes an {@code int} to the underlying output stream as four
+ * bytes, high byte first. If no exception is thrown, the counter
+ * {@code written} is incremented by {@code 4}.
+ *
+ * @param v an {@code int} to be written.
+ * @see SafeFilterOutputStream#out
+ */
+ public final void writeInt(int v) {
+ out.write((v >>> 24) & 0xFF);
+ out.write((v >>> 16) & 0xFF);
+ out.write((v >>> 8) & 0xFF);
+ out.write((v) & 0xFF);
+ incCount(4);
+ }
+
+ private final byte[] writeBuffer = new byte[8];
+
+ /**
+ * Writes a {@code long} to the underlying output stream as eight
+ * bytes, high byte first. In no exception is thrown, the counter
+ * {@code written} is incremented by {@code 8}.
+ *
+ * @param v a {@code long} to be written.
+ * @see SafeFilterOutputStream#out
+ */
+ public final void writeLong(long v) {
+ writeBuffer[0] = (byte)(v >>> 56);
+ writeBuffer[1] = (byte)(v >>> 48);
+ writeBuffer[2] = (byte)(v >>> 40);
+ writeBuffer[3] = (byte)(v >>> 32);
+ writeBuffer[4] = (byte)(v >>> 24);
+ writeBuffer[5] = (byte)(v >>> 16);
+ writeBuffer[6] = (byte)(v >>> 8);
+ writeBuffer[7] = (byte)(v);
+ out.write(writeBuffer, 0, 8);
+ incCount(8);
+ }
+
+ /**
+ * Writes a {@code Int52} to the underlying output stream as seven
+ * bytes, high byte first. In no exception is thrown, the counter
+ * {@code written} is incremented by {@code 7}.
+ *
+ * @param v a {@code Int52} to be written.
+ * @see SafeFilterOutputStream#out
+ */
+ public final void writeInt52(long v) {
+ writeBuffer[0] = (byte)(v >> 48 & 0xf);
+ writeBuffer[1] = (byte)(v >> 40 & 0xff);
+ writeBuffer[2] = (byte)(v >> 32 & 0xff);
+ writeBuffer[3] = (byte)(v >> 24 & 0xff);
+ writeBuffer[4] = (byte)(v >> 16 & 0xff);
+ writeBuffer[5] = (byte)(v >> 8 & 0xff);
+ writeBuffer[6] = (byte)(v & 0xff);
+ out.write(writeBuffer, 0, 7);
+ incCount(7);
+ }
+
+ /**
+ * Converts the float argument to an {@code int} using the
+ * {@code floatToIntBits} method in class {@code Float},
+ * and then writes that {@code int} value to the underlying
+ * output stream as a 4-byte quantity, high byte first. If no
+ * exception is thrown, the counter {@code written} is
+ * incremented by {@code 4}.
+ *
+ * @param v a {@code float} value to be written.
+ * @see SafeFilterOutputStream#out
+ * @see Float#floatToIntBits(float)
+ */
+ public final void writeFloat(float v) {
+ writeInt(Float.floatToIntBits(v));
+ }
+
+ /**
+ * Converts the double argument to a {@code long} using the
+ * {@code doubleToLongBits} method in class {@code Double},
+ * and then writes that {@code long} value to the underlying
+ * output stream as an 8-byte quantity, high byte first. If no
+ * exception is thrown, the counter {@code written} is
+ * incremented by {@code 8}.
+ *
+ * @param v a {@code double} value to be written.
+ * @see SafeFilterOutputStream#out
+ * @see Double#doubleToLongBits(double)
+ */
+ public final void writeDouble(double v) {
+ writeLong(Double.doubleToLongBits(v));
+ }
+
+ /**
+ * Writes out the string to the underlying output stream as a
+ * sequence of bytes. Each character in the string is written out, in
+ * sequence, by discarding its high eight bits. If no exception is
+ * thrown, the counter {@code written} is incremented by the
+ * length of {@code s}.
+ *
+ * @param s a string of bytes to be written.
+ * @see SafeFilterOutputStream#out
+ */
+ public final void writeBytes(String s) {
+ int len = s.length();
+ for (int i = 0 ; i < len ; i++) {
+ out.write((byte)s.charAt(i));
+ }
+ incCount(len);
+ }
+
+ /**
+ * Writes a string to the underlying output stream as a sequence of
+ * characters. Each character is written to the data output stream as
+ * if by the {@code writeChar} method. If no exception is
+ * thrown, the counter {@code written} is incremented by twice
+ * the length of {@code s}.
+ *
+ * @param s a {@code String} value to be written.
+ * @see SafeDataOutputStream#writeChar(int)
+ * @see SafeFilterOutputStream#out
+ */
+ public final void writeChars(String s) {
+ int len = s.length();
+ for (int i = 0 ; i < len ; i++) {
+ int v = s.charAt(i);
+ out.write((v >>> 8) & 0xFF);
+ out.write((v) & 0xFF);
+ }
+ incCount(len * 2);
+ }
+
+ /**
+ * Writes a string to the underlying output stream using
+ * modified UTF-8
+ * encoding in a machine-independent manner.
+ *
+ * First, two bytes are written to the output stream as if by the
+ * {@code writeShort} method giving the number of bytes to
+ * follow. This value is the number of bytes actually written out,
+ * not the length of the string. Following the length, each character
+ * of the string is output, in sequence, using the modified UTF-8 encoding
+ * for the character. If no exception is thrown, the counter
+ * {@code written} is incremented by the total number of
+ * bytes written to the output stream. This will be at least two
+ * plus the length of {@code str}, and at most two plus
+ * thrice the length of {@code str}.
+ *
+ * @param str a string to be written.
+ * @see #writeChars(String)
+ */
+ public final void writeUTF(String str) {
+ writeUTF(str, this);
+ }
+
+ public final void writeUTF(int strlen, int utflen, String str) {
+ writeUTF(strlen, utflen, str, this);
+ }
+
+ /**
+ * Writes a string to the specified DataOutput using
+ * modified UTF-8
+ * encoding in a machine-independent manner.
+ *
+ * First, two bytes are written to out as if by the {@code writeShort}
+ * method giving the number of bytes to follow. This value is the number of
+ * bytes actually written out, not the length of the string. Following the
+ * length, each character of the string is output, in sequence, using the
+ * modified UTF-8 encoding for the character. If no exception is thrown, the
+ * counter {@code written} is incremented by the total number of
+ * bytes written to the output stream. This will be at least two
+ * plus the length of {@code str}, and at most two plus
+ * thrice the length of {@code str}.
+ *
+ * @param str a string to be written.
+ * @param out destination to write to
+ * @return The number of bytes written out.
+ */
+ static int writeUTF(String str, SafeDataOutput out) {
+ int strlen = strLen(str);
+ int utflen = utfLen(str, strlen);
+ return writeUTF(strlen, utflen, str, out);
+ }
+
+ public static int strLen(String str) {
+ return str.length();
+ }
+
+ public static int utfLen(String str, int strLen) {
+ int utflen = strLen; // optimized for ASCII
+
+ for (int i = 0; i < strLen; i++) {
+ int c = str.charAt(i);
+ if (c >= 0x80 || c == 0)
+ utflen += (c >= 0x800) ? 2 : 1;
+ }
+
+ if (utflen > 65535 || /* overflow */ utflen < strLen)
+ throw new IllegalArgumentException(tooLongMsg(str, utflen));
+ return utflen;
+ }
+
+ static int writeUTF(int strlen, int utflen, String str, SafeDataOutput out) {
+ final byte[] bytearr;
+ if (out instanceof SafeDataOutputStream dos) {
+ if (dos.bytearr == null || (dos.bytearr.length < (utflen + 2)))
+ dos.bytearr = new byte[(utflen*2) + 2];
+ bytearr = dos.bytearr;
+ } else {
+ bytearr = new byte[utflen + 2];
+ }
+
+ int count = 0;
+ bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
+ bytearr[count++] = (byte) ((utflen) & 0xFF);
+
+ int i;
+ for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII
+ int c = str.charAt(i);
+ if (c >= 0x80 || c == 0) break;
+ bytearr[count++] = (byte) c;
+ }
+
+ for (; i < strlen; i++) {
+ int c = str.charAt(i);
+ if (c < 0x80 && c != 0) {
+ bytearr[count++] = (byte) c;
+ } else if (c >= 0x800) {
+ bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
+ bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F));
+ bytearr[count++] = (byte) (0x80 | ((c) & 0x3F));
+ } else {
+ bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
+ bytearr[count++] = (byte) (0x80 | ((c) & 0x3F));
+ }
+ }
+ out.write(bytearr, 0, utflen + 2);
+ return utflen + 2;
+ }
+
+ private static String tooLongMsg(String s, int bits32) {
+ int slen = s.length();
+ String head = s.substring(0, 8);
+ String tail = s.substring(slen - 8, slen);
+ // handle int overflow with max 3x expansion
+ long actualLength = (long)slen + Integer.toUnsignedLong(bits32 - slen);
+ return "encoded string (" + head + "..." + tail + ") too long: "
+ + actualLength + " bytes";
+ }
+
+ /**
+ * Returns the current value of the counter {@code written},
+ * the number of bytes written to this data output stream so far.
+ * If the counter overflows, it will be wrapped to Integer.MAX_VALUE.
+ *
+ * @return the value of the {@code written} field.
+ * @see SafeDataOutputStream#written
+ */
+ public final int size() {
+ return written;
+ }
+
+ public DataOutputStream asDataOutputStream() {
+ return new DataOutputStream(this.out);
+ }
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeFilterInputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeFilterInputStream.java
new file mode 100644
index 0000000..230d392
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeFilterInputStream.java
@@ -0,0 +1,210 @@
+package it.cavallium.stream;
+
+/**
+ * A {@code FilterInputStream} contains
+ * some other input stream, which it uses as
+ * its basic source of data, possibly transforming
+ * the data along the way or providing additional
+ * functionality. The class {@code FilterInputStream}
+ * itself simply overrides all methods of
+ * {@code InputStream} with versions that
+ * pass all requests to the contained input
+ * stream. Subclasses of {@code FilterInputStream}
+ * may further override some of these methods
+ * and may also provide additional methods
+ * and fields.
+ *
+ * @author Jonathan Payne
+ * @since 1.0
+ */
+public class SafeFilterInputStream extends SafeInputStream {
+ /**
+ * The input stream to be filtered.
+ */
+ protected volatile SafeInputStream in;
+
+ /**
+ * Creates a {@code FilterInputStream}
+ * by assigning the argument {@code in}
+ * to the field {@code this.in} so as
+ * to remember it for later use.
+ *
+ * @param in the underlying input stream, or {@code null} if
+ * this instance is to be created without an underlying stream.
+ */
+ protected SafeFilterInputStream(SafeInputStream in) {
+ this.in = in;
+ }
+
+ /**
+ * Reads the next byte of data from this input stream. The value
+ * byte is returned as an {@code int} in the range
+ * {@code 0} to {@code 255}. If no byte is available
+ * because the end of the stream has been reached, the value
+ * {@code -1} is returned. This method blocks until input data
+ * is available, the end of the stream is detected, or an exception
+ * is thrown.
+ *
+ * This method
+ * simply performs {@code in.read()} and returns the result.
+ *
+ * @return the next byte of data, or {@code -1} if the end of the
+ * stream is reached.
+ * @see SafeFilterInputStream#in
+ */
+ public int read() {
+ return in.read();
+ }
+
+ /**
+ * Reads up to {@code b.length} bytes of data from this
+ * input stream into an array of bytes. This method blocks until some
+ * input is available.
+ *
+ * This method simply performs the call
+ * {@code read(b, 0, b.length)} and returns
+ * the result. It is important that it does
+ * not do {@code in.read(b)} instead;
+ * certain subclasses of {@code FilterInputStream}
+ * depend on the implementation strategy actually
+ * used.
+ *
+ * @param b the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or
+ * {@code -1} if there is no more data because the end of
+ * the stream has been reached.
+ * @see SafeFilterInputStream#read(byte[], int, int)
+ */
+ public int read(byte b[]) {
+ return read(b, 0, b.length);
+ }
+
+ /**
+ * Reads up to {@code len} bytes of data from this input stream
+ * into an array of bytes. If {@code len} is not zero, the method
+ * blocks until some input is available; otherwise, no
+ * bytes are read and {@code 0} is returned.
+ *
+ * This method simply performs {@code in.read(b, off, len)}
+ * and returns the result.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in the destination array {@code b}
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * {@code -1} if there is no more data because the end of
+ * the stream has been reached.
+ * @throws NullPointerException If {@code b} is {@code null}.
+ * @throws IndexOutOfBoundsException If {@code off} is negative,
+ * {@code len} is negative, or {@code len} is greater than
+ * {@code b.length - off}
+ * @see SafeFilterInputStream#in
+ */
+ public int read(byte b[], int off, int len) {
+ return in.read(b, off, len);
+ }
+
+ /**
+ * Skips over and discards {@code n} bytes of data from the
+ * input stream. The {@code skip} method may, for a variety of
+ * reasons, end up skipping over some smaller number of bytes,
+ * possibly {@code 0}. The actual number of bytes skipped is
+ * returned.
+ *
+ * This method simply performs {@code in.skip(n)}.
+ *
+ * @param n the number of bytes to be skipped.
+ * @return the actual number of bytes skipped.
+ */
+ public long skip(long n) {
+ return in.skip(n);
+ }
+
+ /**
+ * Returns an estimate of the number of bytes that can be read (or
+ * skipped over) from this input stream without blocking by the next
+ * caller of a method for this input stream. The next caller might be
+ * the same thread or another thread. A single read or skip of this
+ * many bytes will not block, but may read or skip fewer bytes.
+ *
+ * This method returns the result of {@link #in in}.available().
+ *
+ * @return an estimate of the number of bytes that can be read (or skipped
+ * over) from this input stream without blocking.
+ */
+ public int available() {
+ return in.available();
+ }
+
+ /**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ * This
+ * method simply performs {@code in.close()}.
+ *
+ * @see SafeFilterInputStream#in
+ */
+ public void close() {
+ in.close();
+ }
+
+ /**
+ * Marks the current position in this input stream. A subsequent
+ * call to the {@code reset} method repositions this stream at
+ * the last marked position so that subsequent reads re-read the same bytes.
+ *
+ * The {@code readlimit} argument tells this input stream to
+ * allow that many bytes to be read before the mark position gets
+ * invalidated.
+ *
+ * This method simply performs {@code in.mark(readlimit)}.
+ *
+ * @param readlimit the maximum limit of bytes that can be read before
+ * the mark position becomes invalid.
+ * @see SafeFilterInputStream#in
+ * @see SafeFilterInputStream#reset()
+ */
+ public void mark(int readlimit) {
+ in.mark(readlimit);
+ }
+
+ /**
+ * Repositions this stream to the position at the time the
+ * {@code mark} method was last called on this input stream.
+ *
+ * This method
+ * simply performs {@code in.reset()}.
+ *
+ * Stream marks are intended to be used in
+ * situations where you need to read ahead a little to see what's in
+ * the stream. Often this is most easily done by invoking some
+ * general parser. If the stream is of the type handled by the
+ * parse, it just chugs along happily. If the stream is not of
+ * that type, the parser should toss an exception when it fails.
+ * If this happens within readlimit bytes, it allows the outer
+ * code to reset the stream and try another parser.
+ *
+ * @see SafeFilterInputStream#in
+ * @see SafeFilterInputStream#mark(int)
+ */
+ public void reset() {
+ in.reset();
+ }
+
+ /**
+ * Tests if this input stream supports the {@code mark}
+ * and {@code reset} methods.
+ * This method
+ * simply performs {@code in.markSupported()}.
+ *
+ * @return {@code true} if this stream type supports the
+ * {@code mark} and {@code reset} method;
+ * {@code false} otherwise.
+ * @see SafeFilterInputStream#in
+ * @see java.io.InputStream#mark(int)
+ * @see java.io.InputStream#reset()
+ */
+ public boolean markSupported() {
+ return in.markSupported();
+ }
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeFilterOutputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeFilterOutputStream.java
new file mode 100644
index 0000000..737cb75
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeFilterOutputStream.java
@@ -0,0 +1,180 @@
+package it.cavallium.stream;
+
+
+/**
+ * This class is the superclass of all classes that filter output
+ * streams. These streams sit on top of an already existing output
+ * stream (the underlying output stream) which it uses as its
+ * basic sink of data, but possibly transforming the data along the
+ * way or providing additional functionality.
+ *
+ * The class {@code FilterOutputStream} itself simply overrides
+ * all methods of {@code SafeOutputStream} with versions that pass
+ * all requests to the underlying output stream. Subclasses of
+ * {@code FilterOutputStream} may further override some of these
+ * methods as well as provide additional methods and fields.
+ *
+ * @author Jonathan Payne
+ * @since 1.0
+ */
+public class SafeFilterOutputStream extends SafeOutputStream {
+ /**
+ * The underlying output stream to be filtered.
+ */
+ protected SafeOutputStream out;
+
+ /**
+ * Whether the stream is closed; implicitly initialized to false.
+ */
+ private volatile boolean closed;
+
+ /**
+ * Object used to prevent a race on the 'closed' instance variable.
+ */
+ private final Object closeLock = new Object();
+
+ /**
+ * Creates an output stream filter built on top of the specified
+ * underlying output stream.
+ *
+ * @param out the underlying output stream to be assigned to
+ * the field {@code this.out} for later use, or
+ * {@code null} if this instance is to be
+ * created without an underlying stream.
+ */
+ public SafeFilterOutputStream(SafeOutputStream out) {
+ this.out = out;
+ }
+
+ /**
+ * Writes the specified {@code byte} to this output stream.
+ *
+ * The {@code write} method of {@code FilterOutputStream}
+ * calls the {@code write} method of its underlying output stream,
+ * that is, it performs {@code out.write(b)}.
+ *
+ * Implements the abstract {@code write} method of {@code SafeOutputStream}.
+ *
+ * @param b the {@code byte}.
+ */
+ @Override
+ public void write(int b) {
+ out.write(b);
+ }
+
+ /**
+ * Writes {@code b.length} bytes to this output stream.
+ *
+ * The {@code write} method of {@code FilterOutputStream}
+ * calls its {@code write} method of three arguments with the
+ * arguments {@code b}, {@code 0}, and
+ * {@code b.length}.
+ *
+ * Note that this method does not call the one-argument
+ * {@code write} method of its underlying output stream with
+ * the single argument {@code b}.
+ *
+ * @param b the data to be written.
+ * @see SafeFilterOutputStream#write(byte[], int, int)
+ */
+ @Override
+ public void write(byte b[]) {
+ write(b, 0, b.length);
+ }
+
+ /**
+ * Writes {@code len} bytes from the specified
+ * {@code byte} array starting at offset {@code off} to
+ * this output stream.
+ *
+ * The {@code write} method of {@code FilterOutputStream}
+ * calls the {@code write} method of one argument on each
+ * {@code byte} to output.
+ *
+ * Note that this method does not call the {@code write} method
+ * of its underlying output stream with the same arguments. Subclasses
+ * of {@code FilterOutputStream} should provide a more efficient
+ * implementation of this method.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ * @see SafeFilterOutputStream#write(int)
+ */
+ @Override
+ public void write(byte b[], int off, int len) {
+ if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
+ throw new IndexOutOfBoundsException();
+
+ for (int i = 0 ; i < len ; i++) {
+ write(b[off + i]);
+ }
+ }
+
+ /**
+ * Flushes this output stream and forces any buffered output bytes
+ * to be written out to the stream.
+ *
+ * The {@code flush} method of {@code FilterOutputStream}
+ * calls the {@code flush} method of its underlying output stream.
+ *
+ * @see SafeFilterOutputStream#out
+ */
+ @Override
+ public void flush() {
+ out.flush();
+ }
+
+ /**
+ * Closes this output stream and releases any system resources
+ * associated with the stream.
+ *
+ * When not already closed, the {@code close} method of {@code
+ * FilterOutputStream} calls its {@code flush} method, and then
+ * calls the {@code close} method of its underlying output stream.
+ *
+ * @see SafeFilterOutputStream#flush()
+ * @see SafeFilterOutputStream#out
+ */
+ @Override
+ public void close() {
+ if (closed) {
+ return;
+ }
+ synchronized (closeLock) {
+ if (closed) {
+ return;
+ }
+ closed = true;
+ }
+
+ Throwable flushException = null;
+ try {
+ flush();
+ } catch (Throwable e) {
+ flushException = e;
+ throw e;
+ } finally {
+ if (flushException == null) {
+ out.close();
+ } else {
+ try {
+ out.close();
+ } catch (Throwable closeException) {
+ // evaluate possible precedence of flushException over closeException
+ if ((flushException instanceof ThreadDeath) &&
+ !(closeException instanceof ThreadDeath)) {
+ flushException.addSuppressed(closeException);
+ throw (ThreadDeath) flushException;
+ }
+
+ if (flushException != closeException) {
+ closeException.addSuppressed(flushException);
+ }
+
+ throw closeException;
+ }
+ }
+ }
+ }
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeInputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeInputStream.java
new file mode 100644
index 0000000..f2c8128
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeInputStream.java
@@ -0,0 +1,201 @@
+package it.cavallium.stream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+public abstract class SafeInputStream extends InputStream {
+
+ // MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
+ // use when skipping.
+ private static final int MAX_SKIP_BUFFER_SIZE = 2048;
+
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ @Override
+ public abstract int read();
+
+ public int read(byte b[]) {
+ return read(b, 0, b.length);
+ }
+
+ public int read(byte b[], int off, int len) {
+ Objects.checkFromIndexSize(off, len, b.length);
+ if (len == 0) {
+ return 0;
+ }
+
+ int c = read();
+ if (c == -1) {
+ return -1;
+ }
+ b[off] = (byte)c;
+
+ int i = 1;
+ for (; i < len ; i++) {
+ c = read();
+ if (c == -1) {
+ break;
+ }
+ b[off + i] = (byte)c;
+ }
+ return i;
+ }
+
+ public byte[] readAllBytes() {
+ return readNBytes(Integer.MAX_VALUE);
+ }
+
+ private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
+
+ public byte[] readNBytes(int len) {
+ if (len < 0) {
+ throw new IllegalArgumentException("len < 0");
+ }
+
+ List This class has two methods, both specified as optional. This apparently bizarre
+ * behaviour is necessary because of wrapper classes which use reflection
+ * to support those methods (see, e.g., {@link MeasurableInputStream}, {@link FastBufferedInputStream} and {@link FastBufferedOutputStream}).
+ *
+ * @since 6.0.0
+ */
+
+public interface SafeMeasurableStream {
+
+ /** Returns the overall length of this stream (optional operation). In most cases, this will require the
+ * stream to perform some extra action, possibly changing the state of the input stream itself (typically, reading
+ * all the bytes up to the end, or flushing on output stream).
+ * Implementing classes should always document what state will the input stream be in
+ * after calling this method, and which kind of exception could be thrown.
+ */
+ long length();
+
+ /** Returns the current position in this stream (optional operation).
+ *
+ * Usually, the position is just the number of bytes read or written
+ * since the stream was opened, but in the case of a
+ * {@link it.unimi.dsi.fastutil.io.RepositionableStream} it
+ * represent the current position.
+ */
+ long position();
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeOutputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeOutputStream.java
new file mode 100644
index 0000000..b03fd6b
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeOutputStream.java
@@ -0,0 +1,169 @@
+package it.cavallium.stream;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.OutputStream;
+import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * This abstract class is the superclass of all classes representing
+ * an output stream of bytes. An output stream accepts output bytes
+ * and sends them to some sink.
+ *
+ * Applications that need to define a subclass of
+ * {@code OutputStream} must always provide at least a method
+ * that writes one byte of output.
+ *
+ * @author Arthur van Hoff
+ * @see java.io.BufferedOutputStream
+ * @see java.io.ByteArrayOutputStream
+ * @see java.io.DataOutputStream
+ * @see java.io.FilterOutputStream
+ * @see java.io.InputStream
+ * @see OutputStream#write(int)
+ * @since 1.0
+ */
+public abstract class SafeOutputStream extends OutputStream implements Closeable, Flushable {
+ /**
+ * Constructor for subclasses to call.
+ */
+ public SafeOutputStream() {}
+
+ /**
+ * Returns a new {@code OutputStream} which discards all bytes. The
+ * returned stream is initially open. The stream is closed by calling
+ * the {@code close()} method. Subsequent calls to {@code close()} have
+ * no effect.
+ *
+ * While the stream is open, the {@code write(int)}, {@code
+ * write(byte[])}, and {@code write(byte[], int, int)} methods do nothing.
+ * After the stream has been closed, these methods all throw {@code
+ * IOException}.
+ *
+ * The {@code flush()} method does nothing.
+ *
+ * @return an {@code OutputStream} which discards all bytes
+ *
+ * @since 11
+ */
+ public static OutputStream nullOutputStream() {
+ return new OutputStream() {
+ private volatile boolean closed;
+
+ private void ensureOpen() {
+ if (closed) {
+ throw new IllegalStateException("Stream closed");
+ }
+ }
+
+ @Override
+ public void write(int b) {
+ ensureOpen();
+ }
+
+ @Override
+ public void write(byte @NotNull [] b, int off, int len) {
+ Objects.checkFromIndexSize(off, len, b.length);
+ ensureOpen();
+ }
+
+ @Override
+ public void close() {
+ closed = true;
+ }
+ };
+ }
+
+ /**
+ * Writes the specified byte to this output stream. The general
+ * contract for {@code write} is that one byte is written
+ * to the output stream. The byte to be written is the eight
+ * low-order bits of the argument {@code b}. The 24
+ * high-order bits of {@code b} are ignored.
+ *
+ * Subclasses of {@code OutputStream} must provide an
+ * implementation for this method.
+ *
+ * @param b the {@code byte}.
+ */
+ public abstract void write(int b);
+
+ /**
+ * Writes {@code b.length} bytes from the specified byte array
+ * to this output stream. The general contract for {@code write(b)}
+ * is that it should have exactly the same effect as the call
+ * {@code write(b, 0, b.length)}.
+ *
+ * @param b the data.
+ * @see OutputStream#write(byte[], int, int)
+ */
+ public void write(byte @NotNull [] b) {
+ write(b, 0, b.length);
+ }
+
+ /**
+ * Writes {@code len} bytes from the specified byte array
+ * starting at offset {@code off} to this output stream.
+ * The general contract for {@code write(b, off, len)} is that
+ * some of the bytes in the array {@code b} are written to the
+ * output stream in order; element {@code b[off]} is the first
+ * byte written and {@code b[off+len-1]} is the last byte written
+ * by this operation.
+ *
+ * The {@code write} method of {@code OutputStream} calls
+ * the write method of one argument on each of the bytes to be
+ * written out. Subclasses are encouraged to override this method and
+ * provide a more efficient implementation.
+ *
+ * If {@code b} is {@code null}, a
+ * {@code NullPointerException} is thrown.
+ *
+ * If {@code off} is negative, or {@code len} is negative, or
+ * {@code off+len} is greater than the length of the array
+ * {@code b}, then an {@code IndexOutOfBoundsException} is thrown.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ */
+ public void write(byte[] b, int off, int len) {
+ Objects.checkFromIndexSize(off, len, b.length);
+ // len == 0 condition implicitly handled by loop bounds
+ for (int i = 0 ; i < len ; i++) {
+ write(b[off + i]);
+ }
+ }
+
+ /**
+ * Flushes this output stream and forces any buffered output bytes
+ * to be written out. The general contract of {@code flush} is
+ * that calling it is an indication that, if any bytes previously
+ * written have been buffered by the implementation of the output
+ * stream, such bytes should immediately be written to their
+ * intended destination.
+ *
+ * If the intended destination of this stream is an abstraction provided by
+ * the underlying operating system, for example a file, then flushing the
+ * stream guarantees only that bytes previously written to the stream are
+ * passed to the operating system for writing; it does not guarantee that
+ * they are actually written to a physical device such as a disk drive.
+ *
+ * The {@code flush} method of {@code OutputStream} does nothing.
+ *
+ */
+ public void flush() {
+ }
+
+ /**
+ * Closes this output stream and releases any system resources
+ * associated with this stream. The general contract of {@code close}
+ * is that it closes the output stream. A closed stream cannot perform
+ * output operations and cannot be reopened.
+ *
+ * The {@code close} method of {@code OutputStream} does nothing.
+ *
+ */
+ public void close() {
+ }
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafePushbackInputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafePushbackInputStream.java
new file mode 100644
index 0000000..e83036b
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafePushbackInputStream.java
@@ -0,0 +1,332 @@
+package it.cavallium.stream;
+
+/**
+ * A {@code PushbackInputStream} adds
+ * functionality to another input stream, namely
+ * the ability to "push back" or "unread" bytes,
+ * by storing pushed-back bytes in an internal buffer.
+ * This is useful in situations where
+ * it is convenient for a fragment of code
+ * to read an indefinite number of data bytes
+ * that are delimited by a particular byte
+ * value; after reading the terminating byte,
+ * the code fragment can "unread" it, so that
+ * the next read operation on the input stream
+ * will reread the byte that was pushed back.
+ * For example, bytes representing the characters
+ * constituting an identifier might be terminated
+ * by a byte representing an operator character;
+ * a method whose job is to read just an identifier
+ * can read until it sees the operator and
+ * then push the operator back to be re-read.
+ *
+ * @author David Connelly
+ * @author Jonathan Payne
+ * @since 1.0
+ */
+public class SafePushbackInputStream extends SafeFilterInputStream {
+ /**
+ * The pushback buffer.
+ * @since 1.1
+ */
+ protected byte[] buf;
+
+ /**
+ * The position within the pushback buffer from which the next byte will
+ * be read. When the buffer is empty, {@code pos} is equal to
+ * {@code buf.length}; when the buffer is full, {@code pos} is
+ * equal to zero.
+ *
+ * @since 1.1
+ */
+ protected int pos;
+
+ /**
+ * Check to make sure that this stream has not been closed
+ */
+ private void ensureOpen() {
+ if (in == null)
+ throw new IllegalStateException("Stream closed");
+ }
+
+ /**
+ * Creates a {@code PushbackInputStream}
+ * with a pushback buffer of the specified {@code size},
+ * and saves its argument, the input stream
+ * {@code in}, for later use. Initially,
+ * the pushback buffer is empty.
+ *
+ * @param in the input stream from which bytes will be read.
+ * @param size the size of the pushback buffer.
+ * @throws IllegalArgumentException if {@code size <= 0}
+ * @since 1.1
+ */
+ public SafePushbackInputStream(SafeInputStream in, int size) {
+ super(in);
+ if (size <= 0) {
+ throw new IllegalArgumentException("size <= 0");
+ }
+ this.buf = new byte[size];
+ this.pos = size;
+ }
+
+ /**
+ * Creates a {@code PushbackInputStream}
+ * with a 1-byte pushback buffer, and saves its argument, the input stream
+ * {@code in}, for later use. Initially,
+ * the pushback buffer is empty.
+ *
+ * @param in the input stream from which bytes will be read.
+ */
+ public SafePushbackInputStream(SafeInputStream in) {
+ this(in, 1);
+ }
+
+ /**
+ * Reads the next byte of data from this input stream. The value
+ * byte is returned as an {@code int} in the range
+ * {@code 0} to {@code 255}. If no byte is available
+ * because the end of the stream has been reached, the value
+ * {@code -1} is returned. This method blocks until input data
+ * is available, the end of the stream is detected, or an exception
+ * is thrown.
+ *
+ * This method returns the most recently pushed-back byte, if there is
+ * one, and otherwise calls the {@code read} method of its underlying
+ * input stream and returns whatever value that method returns.
+ *
+ * @return the next byte of data, or {@code -1} if the end of the
+ * stream has been reached.
+ * or an I/O error occurs.
+ * @see java.io.InputStream#read()
+ */
+ public int read() {
+ ensureOpen();
+ if (pos < buf.length) {
+ return buf[pos++] & 0xff;
+ }
+ return super.read();
+ }
+
+ /**
+ * Reads up to {@code len} bytes of data from this input stream into
+ * an array of bytes. This method first reads any pushed-back bytes; after
+ * that, if fewer than {@code len} bytes have been read then it
+ * reads from the underlying input stream. If {@code len} is not zero, the method
+ * blocks until at least 1 byte of input is available; otherwise, no
+ * bytes are read and {@code 0} is returned.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in the destination array {@code b}
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * {@code -1} if there is no more data because the end of
+ * the stream has been reached.
+ * @throws NullPointerException If {@code b} is {@code null}.
+ * @throws IndexOutOfBoundsException If {@code off} is negative,
+ * {@code len} is negative, or {@code len} is greater than
+ * {@code b.length - off}
+ * or an I/O error occurs.
+ * @see java.io.InputStream#read(byte[], int, int)
+ */
+ public int read(byte[] b, int off, int len) {
+ ensureOpen();
+ if (b == null) {
+ throw new NullPointerException();
+ } else if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ int avail = buf.length - pos;
+ if (avail > 0) {
+ if (len < avail) {
+ avail = len;
+ }
+ System.arraycopy(buf, pos, b, off, avail);
+ pos += avail;
+ off += avail;
+ len -= avail;
+ }
+ if (len > 0) {
+ len = super.read(b, off, len);
+ if (len == -1) {
+ return avail == 0 ? -1 : avail;
+ }
+ return avail + len;
+ }
+ return avail;
+ }
+
+ /**
+ * Pushes back a byte by copying it to the front of the pushback buffer.
+ * After this method returns, the next byte to be read will have the value
+ * {@code (byte)b}.
+ *
+ * @param b the {@code int} value whose low-order
+ * byte is to be pushed back.
+ */
+ public void unread(int b) {
+ ensureOpen();
+ if (pos == 0) {
+ throw new IllegalStateException("Push back buffer is full");
+ }
+ buf[--pos] = (byte)b;
+ }
+
+ /**
+ * Pushes back a portion of an array of bytes by copying it to the front
+ * of the pushback buffer. After this method returns, the next byte to be
+ * read will have the value {@code b[off]}, the byte after that will
+ * have the value {@code b[off+1]}, and so forth.
+ *
+ * @param b the byte array to push back.
+ * @param off the start offset of the data.
+ * @param len the number of bytes to push back.
+ * @throws NullPointerException If {@code b} is {@code null}.
+ * @since 1.1
+ */
+ public void unread(byte[] b, int off, int len) {
+ ensureOpen();
+ if (len > pos) {
+ throw new IllegalStateException("Push back buffer is full");
+ }
+ pos -= len;
+ System.arraycopy(b, off, buf, pos, len);
+ }
+
+ /**
+ * Pushes back an array of bytes by copying it to the front of the
+ * pushback buffer. After this method returns, the next byte to be read
+ * will have the value {@code b[0]}, the byte after that will have the
+ * value {@code b[1]}, and so forth.
+ *
+ * @param b the byte array to push back
+ * @throws NullPointerException If {@code b} is {@code null}.
+ * @since 1.1
+ */
+ public void unread(byte[] b) {
+ unread(b, 0, b.length);
+ }
+
+ /**
+ * Returns an estimate of the number of bytes that can be read (or
+ * skipped over) from this input stream without blocking by the next
+ * invocation of a method for this input stream. The next invocation might be
+ * the same thread or another thread. A single read or skip of this
+ * many bytes will not block, but may read or skip fewer bytes.
+ *
+ * The method returns the sum of the number of bytes that have been
+ * pushed back and the value returned by {@link
+ * SafeFilterInputStream#available available}.
+ *
+ * @return the number of bytes that can be read (or skipped over) from
+ * the input stream without blocking.
+ * @see SafeFilterInputStream#in
+ * @see java.io.InputStream#available()
+ */
+ public int available() {
+ ensureOpen();
+ int n = buf.length - pos;
+ int avail = super.available();
+ return n > (Integer.MAX_VALUE - avail)
+ ? Integer.MAX_VALUE
+ : n + avail;
+ }
+
+ /**
+ * Skips over and discards {@code n} bytes of data from this
+ * input stream. The {@code skip} method may, for a variety of
+ * reasons, end up skipping over some smaller number of bytes,
+ * possibly zero. If {@code n} is negative, no bytes are skipped.
+ *
+ * The {@code skip} method of {@code PushbackInputStream}
+ * first skips over the bytes in the pushback buffer, if any. It then
+ * calls the {@code skip} method of the underlying input stream if
+ * more bytes need to be skipped. The actual number of bytes skipped
+ * is returned.
+ *
+ * @param n {@inheritDoc}
+ * @return {@inheritDoc}
+ * @see SafeFilterInputStream#in
+ * @see java.io.InputStream#skip(long n)
+ * @since 1.2
+ */
+ public long skip(long n) {
+ ensureOpen();
+ if (n <= 0) {
+ return 0;
+ }
+
+ long pskip = buf.length - pos;
+ if (pskip > 0) {
+ if (n < pskip) {
+ pskip = n;
+ }
+ pos += pskip;
+ n -= pskip;
+ }
+ if (n > 0) {
+ pskip += super.skip(n);
+ }
+ return pskip;
+ }
+
+ /**
+ * Tests if this input stream supports the {@code mark} and
+ * {@code reset} methods, which it does not.
+ *
+ * @return {@code false}, since this class does not support the
+ * {@code mark} and {@code reset} methods.
+ * @see java.io.InputStream#mark(int)
+ * @see java.io.InputStream#reset()
+ */
+ public boolean markSupported() {
+ return false;
+ }
+
+ /**
+ * Marks the current position in this input stream.
+ *
+ * The {@code mark} method of {@code PushbackInputStream}
+ * does nothing.
+ *
+ * @param readlimit the maximum limit of bytes that can be read before
+ * the mark position becomes invalid.
+ * @see java.io.InputStream#reset()
+ */
+ public void mark(int readlimit) {
+ }
+
+ /**
+ * Repositions this stream to the position at the time the
+ * {@code mark} method was last called on this input stream.
+ *
+ * The method {@code reset} for class
+ * {@code PushbackInputStream} does nothing except throw an
+ * {@code IOException}.
+ *
+ * @see java.io.InputStream#mark(int)
+ * @see java.io.IOException
+ */
+ public void reset() {
+ throw new UnsupportedOperationException("mark/reset not supported");
+ }
+
+ /**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ * Once the stream has been closed, further read(), unread(),
+ * available(), reset(), or skip() invocations will throw an IOException.
+ * Closing a previously closed stream has no effect.
+ *
+ */
+ public void close() {
+ if (in == null)
+ return;
+ in.close();
+ in = null;
+ buf = null;
+ }
+}
diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeRepositionableStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeRepositionableStream.java
new file mode 100644
index 0000000..1b5a889
--- /dev/null
+++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeRepositionableStream.java
@@ -0,0 +1,40 @@
+package it.cavallium.stream;
+
+/*
+ * Copyright (C) 2005-2020 Sebastiano Vigna
+ *
+ * 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.
+ */
+
+
+/** A basic interface specifying positioning methods for a byte stream.
+ *
+ * @author Sebastiano Vigna
+ * @since 4.4
+ */
+
+public interface SafeRepositionableStream {
+
+ /** Sets the current stream position.
+ *
+ * @param newPosition the new stream position.
+ */
+ void position(long newPosition);
+
+ /** Returns the current stream position.
+ *
+ * @return the current stream position.
+ */
+ long position();
+
+}
diff --git a/data-generator-runtime/src/main/java/module-info.java b/data-generator-runtime/src/main/java/module-info.java
index cc2d60f..e34c3e2 100644
--- a/data-generator-runtime/src/main/java/module-info.java
+++ b/data-generator-runtime/src/main/java/module-info.java
@@ -1,6 +1,9 @@
module data.generator.runtime {
exports it.cavallium.data.generator.nativedata;
exports it.cavallium.data.generator;
+ exports it.cavallium.stream;
+ exports it.cavallium.buffer;
+
requires org.jetbrains.annotations;
requires it.unimi.dsi.fastutil;
}
\ No newline at end of file
diff --git a/data-generator-runtime/src/test/java/it/cavallium/data/generator/nativedata/TestInt52Serializer.java b/data-generator-runtime/src/test/java/it/cavallium/data/generator/nativedata/TestInt52Serializer.java
index ca1a5a6..d954cde 100644
--- a/data-generator-runtime/src/test/java/it/cavallium/data/generator/nativedata/TestInt52Serializer.java
+++ b/data-generator-runtime/src/test/java/it/cavallium/data/generator/nativedata/TestInt52Serializer.java
@@ -1,9 +1,9 @@
package it.cavallium.data.generator.nativedata;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
+import it.cavallium.stream.SafeByteArrayInputStream;
+import it.cavallium.stream.SafeByteArrayOutputStream;
+import it.cavallium.stream.SafeDataInputStream;
+import it.cavallium.stream.SafeDataOutputStream;
import java.io.IOException;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Assertions;
@@ -24,15 +24,15 @@ public class TestInt52Serializer {
public void testInt52Serialization(long n) throws IOException {
var serializer = new Int52Serializer();
byte[] out;
- try (var baos = new ByteArrayOutputStream()) {
- try (var dos = new DataOutputStream(baos)) {
+ try (var baos = new SafeByteArrayOutputStream()) {
+ try (var dos = new SafeDataOutputStream(baos)) {
serializer.serialize(dos, Int52.fromLong(n));
}
out = baos.toByteArray();
}
- var bais = new ByteArrayInputStream(out);
- var dis = new DataInputStream(bais);
+ var bais = new SafeByteArrayInputStream(out);
+ var dis = new SafeDataInputStream(bais);
Assertions.assertEquals(n, serializer.deserialize(dis).longValue(), "Deserialized number differ");
}
@@ -48,8 +48,8 @@ public class TestInt52Serializer {
public void testInt52OptionalSerialization(@Nullable Long n) throws IOException {
var serializer = new NullableInt52Serializer();
byte[] out;
- try (var baos = new ByteArrayOutputStream()) {
- try (var dos = new DataOutputStream(baos)) {
+ try (var baos = new SafeByteArrayOutputStream()) {
+ try (var dos = new SafeDataOutputStream(baos)) {
if (n == null) {
serializer.serialize(dos, NullableInt52.empty());
} else {
@@ -59,8 +59,8 @@ public class TestInt52Serializer {
out = baos.toByteArray();
}
- var bais = new ByteArrayInputStream(out);
- var dis = new DataInputStream(bais);
+ var bais = new SafeByteArrayInputStream(out);
+ var dis = new SafeDataInputStream(bais);
if (n == null) {
Assertions.assertNull(serializer.deserialize(dis).getNullable(), "Deserialized number is not empty");
} else {
> {
@Override
- public void serialize(DataOutput dataOutput, @NotNull List
> {
@NotNull
@Override
- public List
+ *
+ * @param b the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or
+ * {@code -1} if there is no more data because the end
+ * of the stream has been reached.
+ * @see SafeFilterInputStream#in
+ * @see java.io.InputStream#read(byte[], int, int)
+ */
+ int read(byte[] b);
+
+ /**
+ * Reads up to {@code len} bytes of data from the contained
+ * input stream into an array of bytes. An attempt is made to read
+ * as many as {@code len} bytes, but a smaller number may be read,
+ * possibly zero. The number of bytes actually read is returned as an
+ * integer.
+ *
+ *
+ * read(b, 0, b.length)
+ *
+ * with:
+ *
+ * DataInputStream d = new DataInputStream(in);
+ *
+ *
+ * @return the next line of text from this input stream.
+ * @see java.io.BufferedReader#readLine()
+ * @see SafeFilterInputStream#in
+ */
+ @Override
+ @Deprecated
+ public final String readLine() {
+ char[] buf = lineBuffer;
+
+ if (buf == null) {
+ buf = lineBuffer = new char[128];
+ }
+
+ int room = buf.length;
+ int offset = 0;
+ int c;
+
+ loop: while (true) {
+ switch (c = in.read()) {
+ case -1:
+ case '\n':
+ break loop;
+
+ case '\r':
+ int c2 = in.read();
+ if ((c2 != '\n') && (c2 != -1)) {
+ if (!(in instanceof SafePushbackInputStream)) {
+ this.in = new SafePushbackInputStream(in);
+ }
+ ((SafePushbackInputStream)in).unread(c2);
+ }
+ break loop;
+
+ default:
+ if (--room < 0) {
+ buf = new char[offset + 128];
+ room = buf.length - offset - 1;
+ System.arraycopy(lineBuffer, 0, buf, 0, offset);
+ lineBuffer = buf;
+ }
+ buf[offset++] = (char) c;
+ break;
+ }
+ }
+ if ((c == -1) && (offset == 0)) {
+ return null;
+ }
+ return String.copyValueOf(buf, 0, offset);
+ }
+
+ /**
+ * See the general contract of the {@code readUTF}
+ * method of {@code DataInput}.
+ *
+ * BufferedReader d
+ * = new BufferedReader(new InputStreamReader(in));
+ *
{@code
+ * (byte)(0xff & (v >> 8))
+ * (byte)(0xff & v)
+ * }
{@code
+ * (byte)(0xff & (v >> 8))
+ * (byte)(0xff & v)
+ * }
{@code
+ * (byte)(0xff & (v >> 24))
+ * (byte)(0xff & (v >> 16))
+ * (byte)(0xff & (v >> 8))
+ * (byte)(0xff & v)
+ * }
{@code
+ * (byte)(0xff & (v >> 56))
+ * (byte)(0xff & (v >> 48))
+ * (byte)(0xff & (v >> 40))
+ * (byte)(0xff & (v >> 32))
+ * (byte)(0xff & (v >> 24))
+ * (byte)(0xff & (v >> 16))
+ * (byte)(0xff & (v >> 8))
+ * (byte)(0xff & v)
+ * }
{@code
+ * (byte)(v >> 48 & 0xf);
+ * (byte)(v >> 40 & 0xff);
+ * (byte)(v >> 32 & 0xff);
+ * (byte)(v >> 24 & 0xff);
+ * (byte)(v >> 16 & 0xff);
+ * (byte)(v >> 8 & 0xff);
+ * (byte)(v & 0xff);
+ * }
\u0001
through
+ * \u007f
, it is represented
+ * by one byte:
+ * (byte)c
\u0000
+ * or is in the range \u0080
+ * through \u07ff
, then it is
+ * represented by two bytes, to be written
+ * in the order shown: {@code
+ * (byte)(0xc0 | (0x1f & (c >> 6)))
+ * (byte)(0x80 | (0x3f & c))
+ * }
\u0800
+ * through {@code uffff}, then it is
+ * represented by three bytes, to be written
+ * in the order shown: {@code
+ * (byte)(0xe0 | (0x0f & (c >> 12)))
+ * (byte)(0x80 | (0x3f & (c >> 6)))
+ * (byte)(0x80 | (0x3f & c))
+ * }