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 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 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 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 { - void serialize(DataOutput dataOutput, @NotNull T data) throws IOException; + void serialize(SafeDataOutput dataOutput, @NotNull T data); - @NotNull T deserialize(DataInput dataInput) throws IOException; + @NotNull T deserialize(SafeDataInput dataInput); } diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayInt52Serializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayInt52Serializer.java index 280740b..4a60816 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayInt52Serializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayInt52Serializer.java @@ -1,16 +1,15 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import java.util.List; import org.jetbrains.annotations.NotNull; public class ArrayInt52Serializer implements DataSerializer< List> { @Override - public void serialize(DataOutput dataOutput, List data) throws IOException { + public void serialize(SafeDataOutput dataOutput, List data) { dataOutput.writeInt(data.size()); for (Int52 item : data) { Int52Serializer.INSTANCE.serialize(dataOutput, item); @@ -19,7 +18,7 @@ public class ArrayInt52Serializer implements DataSerializer< List> { @NotNull @Override - public List deserialize(DataInput dataInput) throws IOException { + public List deserialize(SafeDataInput dataInput) { var data = new Int52[dataInput.readInt()]; for (int i = 0; i < data.length; i++) { data[i] = Int52Serializer.INSTANCE.deserialize(dataInput); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayStringSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayStringSerializer.java index 767c06e..fb7e971 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayStringSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayStringSerializer.java @@ -1,16 +1,15 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import java.util.List; import org.jetbrains.annotations.NotNull; public class ArrayStringSerializer implements DataSerializer> { @Override - public void serialize(DataOutput dataOutput, @NotNull List data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull List data) { dataOutput.writeInt(data.size()); for (String item : data) { dataOutput.writeUTF(item); @@ -19,7 +18,7 @@ public class ArrayStringSerializer implements DataSerializer> { @NotNull @Override - public List deserialize(DataInput dataInput) throws IOException { + public List deserialize(SafeDataInput dataInput) { var data = new String[dataInput.readInt()]; for (int i = 0; i < data.length; i++) { data[i] = dataInput.readUTF(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraybooleanSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraybooleanSerializer.java index e80357c..4fe0dd6 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraybooleanSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraybooleanSerializer.java @@ -1,16 +1,15 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import it.unimi.dsi.fastutil.booleans.BooleanList; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import org.jetbrains.annotations.NotNull; public class ArraybooleanSerializer implements DataSerializer { @Override - public void serialize(DataOutput dataOutput, @NotNull BooleanList data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull BooleanList data) { dataOutput.writeInt(data.size()); for (int i = 0; i < data.size(); i++) { dataOutput.writeBoolean(data.getBoolean(i)); @@ -19,7 +18,7 @@ public class ArraybooleanSerializer implements DataSerializer { @NotNull @Override - public BooleanList deserialize(DataInput dataInput) throws IOException { + public BooleanList deserialize(SafeDataInput dataInput) { var data = new boolean[dataInput.readInt()]; for (int i = 0; i < data.length; i++) { data[i] = dataInput.readBoolean(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraybyteSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraybyteSerializer.java index f5bff1e..00c0644 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraybyteSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraybyteSerializer.java @@ -1,16 +1,15 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import it.unimi.dsi.fastutil.bytes.ByteList; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import org.jetbrains.annotations.NotNull; public class ArraybyteSerializer implements DataSerializer { @Override - public void serialize(DataOutput dataOutput, @NotNull ByteList data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull ByteList data) { dataOutput.writeInt(data.size()); for (int i = 0; i < data.size(); i++) { dataOutput.writeByte(data.getByte(i)); @@ -19,7 +18,7 @@ public class ArraybyteSerializer implements DataSerializer { @NotNull @Override - public ByteList deserialize(DataInput dataInput) throws IOException { + public ByteList deserialize(SafeDataInput dataInput) { var data = new byte[dataInput.readInt()]; for (int i = 0; i < data.length; i++) { data[i] = dataInput.readByte(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraycharSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraycharSerializer.java index e1b37e2..dd1d527 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraycharSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraycharSerializer.java @@ -1,16 +1,15 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import it.unimi.dsi.fastutil.chars.CharList; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import org.jetbrains.annotations.NotNull; public class ArraycharSerializer implements DataSerializer { @Override - public void serialize(DataOutput dataOutput, @NotNull CharList data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull CharList data) { dataOutput.writeInt(data.size()); for (int i = 0; i < data.size(); i++) { dataOutput.writeChar(data.getChar(i)); @@ -19,7 +18,7 @@ public class ArraycharSerializer implements DataSerializer { @NotNull @Override - public CharList deserialize(DataInput dataInput) throws IOException { + public CharList deserialize(SafeDataInput dataInput) { var data = new char[dataInput.readInt()]; for (int i = 0; i < data.length; i++) { data[i] = dataInput.readChar(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraydoubleSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraydoubleSerializer.java index 8c99145..e5f1563 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraydoubleSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraydoubleSerializer.java @@ -1,16 +1,15 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import it.unimi.dsi.fastutil.doubles.DoubleList; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import org.jetbrains.annotations.NotNull; public class ArraydoubleSerializer implements DataSerializer { @Override - public void serialize(DataOutput dataOutput, @NotNull DoubleList data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull DoubleList data) { dataOutput.writeInt(data.size()); for (int i = 0; i < data.size(); i++) { dataOutput.writeDouble(data.getDouble(i)); @@ -19,7 +18,7 @@ public class ArraydoubleSerializer implements DataSerializer { @NotNull @Override - public DoubleList deserialize(DataInput dataInput) throws IOException { + public DoubleList deserialize(SafeDataInput dataInput) { var data = new double[dataInput.readInt()]; for (int i = 0; i < data.length; i++) { data[i] = dataInput.readDouble(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayfloatSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayfloatSerializer.java index 712f6ba..72cb63c 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayfloatSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayfloatSerializer.java @@ -1,16 +1,15 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import it.unimi.dsi.fastutil.floats.FloatList; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import org.jetbrains.annotations.NotNull; public class ArrayfloatSerializer implements DataSerializer { @Override - public void serialize(DataOutput dataOutput, @NotNull FloatList data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull FloatList data) { dataOutput.writeInt(data.size()); for (int i = 0; i < data.size(); i++) { dataOutput.writeFloat(data.getFloat(i)); @@ -19,7 +18,7 @@ public class ArrayfloatSerializer implements DataSerializer { @NotNull @Override - public FloatList deserialize(DataInput dataInput) throws IOException { + public FloatList deserialize(SafeDataInput dataInput) { var data = new float[dataInput.readInt()]; for (int i = 0; i < data.length; i++) { data[i] = dataInput.readFloat(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayintSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayintSerializer.java index fdf3739..bac4bac 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayintSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayintSerializer.java @@ -1,16 +1,15 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import it.unimi.dsi.fastutil.ints.IntList; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import org.jetbrains.annotations.NotNull; public class ArrayintSerializer implements DataSerializer { @Override - public void serialize(DataOutput dataOutput, @NotNull IntList data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull IntList data) { dataOutput.writeInt(data.size()); for (int i = 0; i < data.size(); i++) { dataOutput.writeInt(data.getInt(i)); @@ -19,7 +18,7 @@ public class ArrayintSerializer implements DataSerializer { @NotNull @Override - public IntList deserialize(DataInput dataInput) throws IOException { + public IntList deserialize(SafeDataInput dataInput) { var data = new int[dataInput.readInt()]; for (int i = 0; i < data.length; i++) { data[i] = dataInput.readInt(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraylongSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraylongSerializer.java index 1b93e28..f361aa6 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraylongSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArraylongSerializer.java @@ -1,16 +1,15 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import it.unimi.dsi.fastutil.longs.LongList; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import org.jetbrains.annotations.NotNull; public class ArraylongSerializer implements DataSerializer { @Override - public void serialize(DataOutput dataOutput, @NotNull LongList data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull LongList data) { dataOutput.writeInt(data.size()); for (int i = 0; i < data.size(); i++) { dataOutput.writeLong(data.getLong(i)); @@ -19,7 +18,7 @@ public class ArraylongSerializer implements DataSerializer { @NotNull @Override - public LongList deserialize(DataInput dataInput) throws IOException { + public LongList deserialize(SafeDataInput dataInput) { var data = new long[dataInput.readInt()]; for (int i = 0; i < data.length; i++) { data[i] = dataInput.readLong(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayshortSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayshortSerializer.java index a1e43b2..abbf6ad 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayshortSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ArrayshortSerializer.java @@ -1,16 +1,15 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import it.unimi.dsi.fastutil.shorts.ShortList; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import org.jetbrains.annotations.NotNull; public class ArrayshortSerializer implements DataSerializer { @Override - public void serialize(DataOutput dataOutput, @NotNull ShortList data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull ShortList data) { dataOutput.writeInt(data.size()); for (int i = 0; i < data.size(); i++) { dataOutput.writeShort(data.getShort(i)); @@ -19,7 +18,7 @@ public class ArrayshortSerializer implements DataSerializer { @NotNull @Override - public ShortList deserialize(DataInput dataInput) throws IOException { + public ShortList deserialize(SafeDataInput dataInput) { var data = new short[dataInput.readInt()]; for (int i = 0; i < data.length; i++) { data[i] = dataInput.readShort(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/Int52Serializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/Int52Serializer.java index efa0260..0d6da3d 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/Int52Serializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/Int52Serializer.java @@ -1,9 +1,8 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -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; public class Int52Serializer implements DataSerializer { @@ -11,17 +10,17 @@ public class Int52Serializer implements DataSerializer { public static final Int52Serializer INSTANCE = new Int52Serializer(); @Override - public void serialize(DataOutput dataOutput, @NotNull Int52 data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull Int52 data) { serializeValue(dataOutput, data); } @NotNull @Override - public Int52 deserialize(DataInput dataInput) throws IOException { + public Int52 deserialize(SafeDataInput dataInput) { return deserializeValue(dataInput); } - public static void serializeValue(DataOutput dataOutput, @NotNull Int52 data) throws IOException { + public static void serializeValue(SafeDataOutput dataOutput, @NotNull Int52 data) { long value = data.getValue(); for(int i = 0; i < 7; i++) { @@ -29,18 +28,9 @@ public class Int52Serializer implements DataSerializer { } } - public static Int52 deserializeValue(DataInput dataInput) throws IOException { - long value = 0; - - return Int52.fromLong( - ((long) dataInput.readUnsignedByte() & 0b00001111) << 48 - | ((long) dataInput.readUnsignedByte()) << 40 - | ((long) dataInput.readUnsignedByte() << 32) - | ((long) dataInput.readUnsignedByte() << 24) - | ((long) dataInput.readUnsignedByte()) << 16 - | ((long) dataInput.readUnsignedByte()) << 8 - | ((long) dataInput.readUnsignedByte()) - ); + public static Int52 deserializeValue(SafeDataInput dataInput) { + long value = dataInput.readInt52(); + return Int52.fromLong(value); } public static byte[] toByteArray(long value) { diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableInt52Serializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableInt52Serializer.java index dcb22eb..7db6380 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableInt52Serializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableInt52Serializer.java @@ -1,9 +1,8 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -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; public class NullableInt52Serializer implements DataSerializer { @@ -11,7 +10,7 @@ public class NullableInt52Serializer implements DataSerializer { public static final NullableInt52Serializer INSTANCE = new NullableInt52Serializer(); @Override - public void serialize(DataOutput dataOutput, @NotNull NullableInt52 data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull NullableInt52 data) { // 0b10000000 = empty, 0b00000000 = with value if (data.isEmpty()) { dataOutput.writeByte(0b10000000); @@ -22,7 +21,7 @@ public class NullableInt52Serializer implements DataSerializer { @NotNull @Override - public NullableInt52 deserialize(DataInput dataInput) throws IOException { + public NullableInt52 deserialize(SafeDataInput dataInput) { // 0b10000000 = empty, 0b00000000 = with value byte firstByteAndIsPresent = dataInput.readByte(); if ((firstByteAndIsPresent & 0b10000000) != 0) { diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableStringSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableStringSerializer.java index 6bcc6cd..2d8a845 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableStringSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableStringSerializer.java @@ -1,9 +1,8 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -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; public class NullableStringSerializer implements DataSerializer { @@ -11,7 +10,7 @@ public class NullableStringSerializer implements DataSerializer public static final NullableStringSerializer INSTANCE = new NullableStringSerializer(); @Override - public void serialize(DataOutput dataOutput, @NotNull NullableString data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull NullableString data) { if (data.isEmpty()) { dataOutput.writeBoolean(false); } else { @@ -23,7 +22,7 @@ public class NullableStringSerializer implements DataSerializer @NotNull @Override - public NullableString deserialize(DataInput dataInput) throws IOException { + public NullableString deserialize(SafeDataInput dataInput) { var isPresent = dataInput.readBoolean(); if (!isPresent) { return NullableString.empty(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablebooleanSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablebooleanSerializer.java index dad1d70..104444a 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablebooleanSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablebooleanSerializer.java @@ -1,9 +1,8 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -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; public class NullablebooleanSerializer implements DataSerializer { @@ -11,7 +10,7 @@ public class NullablebooleanSerializer implements DataSerializer { @@ -11,7 +10,7 @@ public class NullablebyteSerializer implements DataSerializer { public static final NullablebyteSerializer INSTANCE = new NullablebyteSerializer(); @Override - public void serialize(DataOutput dataOutput, @NotNull Nullablebyte data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull Nullablebyte data) { if (data.isEmpty()) { dataOutput.writeBoolean(false); } else { @@ -23,7 +22,7 @@ public class NullablebyteSerializer implements DataSerializer { @NotNull @Override - public Nullablebyte deserialize(DataInput dataInput) throws IOException { + public Nullablebyte deserialize(SafeDataInput dataInput) { var isPresent = dataInput.readBoolean(); if (!isPresent) { return Nullablebyte.empty(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablecharSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablecharSerializer.java index c9a4d8b..98e99e0 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablecharSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablecharSerializer.java @@ -1,9 +1,8 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -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; public class NullablecharSerializer implements DataSerializer { @@ -11,7 +10,7 @@ public class NullablecharSerializer implements DataSerializer { public static final NullablecharSerializer INSTANCE = new NullablecharSerializer(); @Override - public void serialize(DataOutput dataOutput, @NotNull Nullablechar data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull Nullablechar data) { if (data.isEmpty()) { dataOutput.writeBoolean(false); } else { @@ -23,7 +22,7 @@ public class NullablecharSerializer implements DataSerializer { @NotNull @Override - public Nullablechar deserialize(DataInput dataInput) throws IOException { + public Nullablechar deserialize(SafeDataInput dataInput) { var isPresent = dataInput.readBoolean(); if (!isPresent) { return Nullablechar.empty(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullabledoubleSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullabledoubleSerializer.java index 0904bb5..f409b2b 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullabledoubleSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullabledoubleSerializer.java @@ -1,9 +1,8 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -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; public class NullabledoubleSerializer implements DataSerializer { @@ -11,7 +10,7 @@ public class NullabledoubleSerializer implements DataSerializer public static final NullabledoubleSerializer INSTANCE = new NullabledoubleSerializer(); @Override - public void serialize(DataOutput dataOutput, @NotNull Nullabledouble data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull Nullabledouble data) { if (data.isEmpty()) { dataOutput.writeBoolean(false); } else { @@ -23,7 +22,7 @@ public class NullabledoubleSerializer implements DataSerializer @NotNull @Override - public Nullabledouble deserialize(DataInput dataInput) throws IOException { + public Nullabledouble deserialize(SafeDataInput dataInput) { var isPresent = dataInput.readBoolean(); if (!isPresent) { return Nullabledouble.empty(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablefloatSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablefloatSerializer.java index 2d8b150..aadf643 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablefloatSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablefloatSerializer.java @@ -1,9 +1,8 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -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; public class NullablefloatSerializer implements DataSerializer { @@ -11,7 +10,7 @@ public class NullablefloatSerializer implements DataSerializer { public static final NullablefloatSerializer INSTANCE = new NullablefloatSerializer(); @Override - public void serialize(DataOutput dataOutput, @NotNull Nullablefloat data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull Nullablefloat data) { if (data.isEmpty()) { dataOutput.writeBoolean(false); } else { @@ -23,7 +22,7 @@ public class NullablefloatSerializer implements DataSerializer { @NotNull @Override - public Nullablefloat deserialize(DataInput dataInput) throws IOException { + public Nullablefloat deserialize(SafeDataInput dataInput) { var isPresent = dataInput.readBoolean(); if (!isPresent) { return Nullablefloat.empty(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableintSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableintSerializer.java index f9836db..7074218 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableintSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableintSerializer.java @@ -1,9 +1,8 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -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; public class NullableintSerializer implements DataSerializer { @@ -11,7 +10,7 @@ public class NullableintSerializer implements DataSerializer { public static final NullableintSerializer INSTANCE = new NullableintSerializer(); @Override - public void serialize(DataOutput dataOutput, @NotNull Nullableint data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull Nullableint data) { if (data.isEmpty()) { dataOutput.writeBoolean(false); } else { @@ -23,7 +22,7 @@ public class NullableintSerializer implements DataSerializer { @NotNull @Override - public Nullableint deserialize(DataInput dataInput) throws IOException { + public Nullableint deserialize(SafeDataInput dataInput) { var isPresent = dataInput.readBoolean(); if (!isPresent) { return Nullableint.empty(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablelongSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablelongSerializer.java index 3b380c5..37281aa 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablelongSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullablelongSerializer.java @@ -1,9 +1,8 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -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; public class NullablelongSerializer implements DataSerializer { @@ -11,7 +10,7 @@ public class NullablelongSerializer implements DataSerializer { public static final NullablelongSerializer INSTANCE = new NullablelongSerializer(); @Override - public void serialize(DataOutput dataOutput, @NotNull Nullablelong data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull Nullablelong data) { if (data.isEmpty()) { dataOutput.writeBoolean(false); } else { @@ -23,7 +22,7 @@ public class NullablelongSerializer implements DataSerializer { @NotNull @Override - public Nullablelong deserialize(DataInput dataInput) throws IOException { + public Nullablelong deserialize(SafeDataInput dataInput) { var isPresent = dataInput.readBoolean(); if (!isPresent) { return Nullablelong.empty(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableshortSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableshortSerializer.java index b091a1a..0603eaf 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableshortSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/NullableshortSerializer.java @@ -1,9 +1,8 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -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; public class NullableshortSerializer implements DataSerializer { @@ -11,7 +10,7 @@ public class NullableshortSerializer implements DataSerializer { public static final NullableshortSerializer INSTANCE = new NullableshortSerializer(); @Override - public void serialize(DataOutput dataOutput, @NotNull Nullableshort data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull Nullableshort data) { if (data.isEmpty()) { dataOutput.writeBoolean(false); } else { @@ -23,7 +22,7 @@ public class NullableshortSerializer implements DataSerializer { @NotNull @Override - public Nullableshort deserialize(DataInput dataInput) throws IOException { + public Nullableshort deserialize(SafeDataInput dataInput) { var isPresent = dataInput.readBoolean(); if (!isPresent) { return Nullableshort.empty(); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/StringSerializer.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/StringSerializer.java index 0628a6d..9ba6a70 100644 --- a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/StringSerializer.java +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/StringSerializer.java @@ -1,19 +1,16 @@ package it.cavallium.data.generator.nativedata; import it.cavallium.data.generator.DataSerializer; -import java.io.DataInput; -import java.io.DataOutput; +import it.cavallium.stream.SafeDataInput; +import it.cavallium.stream.SafeDataOutput; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; -import java.nio.channels.Channels; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.CodingErrorAction; -import java.nio.charset.MalformedInputException; import java.nio.charset.StandardCharsets; -import java.nio.charset.UnmappableCharacterException; import org.jetbrains.annotations.NotNull; public class StringSerializer implements DataSerializer { @@ -31,7 +28,7 @@ public class StringSerializer implements DataSerializer { ); @Override - public void serialize(DataOutput dataOutput, @NotNull String data) throws IOException { + public void serialize(SafeDataOutput dataOutput, @NotNull String data) { try { var bytes = UTF8_ENCODER.get().reset().encode(CharBuffer.wrap(data)); @@ -43,21 +40,21 @@ public class StringSerializer implements DataSerializer { dataOutput.writeByte(bytes.get()); } } - } catch (IllegalStateException | CharacterCodingException ex) { - throw new IOException("Can't encode this UTF-8 string", ex); + } catch (CharacterCodingException ex) { + throw new IllegalStateException("Can't encode this UTF-8 string", ex); } } @NotNull @Override - public String deserialize(DataInput dataInput) throws IOException { + public String deserialize(SafeDataInput dataInput) { byte[] bytes = new byte[dataInput.readInt()]; dataInput.readFully(bytes); try { CharBuffer decoded = UTF8_DECODER.get().reset().decode(ByteBuffer.wrap(bytes)); return decoded.toString(); } catch (IllegalStateException | CharacterCodingException ex) { - throw new IOException("Can't decode this UTF-8 string", ex); + throw new IllegalStateException("Can't decode this UTF-8 string", ex); } } } diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/ByteBufferBackedInputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/ByteBufferBackedInputStream.java new file mode 100644 index 0000000..bde42a0 --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/stream/ByteBufferBackedInputStream.java @@ -0,0 +1,28 @@ +package it.cavallium.stream; + +import java.io.InputStream; +import java.nio.ByteBuffer; +import org.jetbrains.annotations.NotNull; + +/** + * Simple {@link InputStream} implementation that exposes currently + * available content of a {@link ByteBuffer}. + */ +public class ByteBufferBackedInputStream extends InputStream { + protected final ByteBuffer _b; + + public ByteBufferBackedInputStream(ByteBuffer buf) { _b = buf; } + + @Override public int available() { return _b.remaining(); } + + @Override + public int read() { return _b.hasRemaining() ? (_b.get() & 0xFF) : -1; } + + @Override + public int read(byte @NotNull [] bytes, int off, int len) { + if (!_b.hasRemaining()) return -1; + len = Math.min(len, _b.remaining()); + _b.get(bytes, off, len); + return len; + } +} \ No newline at end of file diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/DataInputOutput.java b/data-generator-runtime/src/main/java/it/cavallium/stream/DataInputOutput.java new file mode 100644 index 0000000..edd739f --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/stream/DataInputOutput.java @@ -0,0 +1,11 @@ +package it.cavallium.stream; + +import java.io.DataInput; +import java.io.DataOutput; + +public interface DataInputOutput extends DataInput, DataOutput { + + DataInput getIn(); + + DataOutput getOut(); +} diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/DataInputOutputImpl.java b/data-generator-runtime/src/main/java/it/cavallium/stream/DataInputOutputImpl.java new file mode 100644 index 0000000..91a4a6f --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/stream/DataInputOutputImpl.java @@ -0,0 +1,173 @@ +package it.cavallium.stream; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class DataInputOutputImpl implements DataInputOutput { + + private final DataInput in; + private final DataOutput out; + + public DataInputOutputImpl(DataInput in, DataOutput out) { + this.in = in; + this.out = out; + } + + @Override + public DataInput getIn() { + return this; + } + + @Override + public DataOutput getOut() { + return this; + } + + @Override + public void readFully(byte @NotNull [] bytes) throws IOException { + in.readFully(bytes); + } + + @Override + public void readFully(byte @NotNull [] bytes, int i, int i1) throws IOException { + in.readFully(bytes, i, i1); + } + + @Override + public int skipBytes(int i) throws IOException { + return in.skipBytes(i); + } + + @Override + public boolean readBoolean() throws IOException { + return in.readBoolean(); + } + + @Override + public byte readByte() throws IOException { + return in.readByte(); + } + + @Override + public int readUnsignedByte() throws IOException { + return in.readUnsignedByte(); + } + + @Override + public short readShort() throws IOException { + return in.readShort(); + } + + @Override + public int readUnsignedShort() throws IOException { + return in.readUnsignedShort(); + } + + @Override + public char readChar() throws IOException { + return in.readChar(); + } + + @Override + public int readInt() throws IOException { + return in.readInt(); + } + + @Override + public long readLong() throws IOException { + return in.readLong(); + } + + @Override + public float readFloat() throws IOException { + return in.readFloat(); + } + + @Override + public double readDouble() throws IOException { + return in.readDouble(); + } + + @Override + public String readLine() throws IOException { + return in.readLine(); + } + + @NotNull + @Override + public String readUTF() throws IOException { + return in.readUTF(); + } + + @Override + public void write(int i) throws IOException { + out.write(i); + } + + @Override + public void write(byte @NotNull [] bytes) throws IOException { + out.write(bytes); + } + + @Override + public void write(byte @NotNull [] bytes, int i, int i1) throws IOException { + out.write(bytes, i, i1); + } + + @Override + public void writeBoolean(boolean b) throws IOException { + out.writeBoolean(b); + } + + @Override + public void writeByte(int i) throws IOException { + out.writeByte(i); + } + + @Override + public void writeShort(int i) throws IOException { + out.writeShort(i); + } + + @Override + public void writeChar(int i) throws IOException { + out.writeChar(i); + } + + @Override + public void writeInt(int i) throws IOException { + out.writeInt(i); + } + + @Override + public void writeLong(long l) throws IOException { + out.writeLong(l); + } + + @Override + public void writeFloat(float v) throws IOException { + out.writeFloat(v); + } + + @Override + public void writeDouble(double v) throws IOException { + out.writeDouble(v); + } + + @Override + public void writeBytes(@NotNull String s) throws IOException { + out.writeBytes(s); + } + + @Override + public void writeChars(@NotNull String s) throws IOException { + out.writeChars(s); + } + + @Override + public void writeUTF(@NotNull String s) throws IOException { + out.writeUTF(s); + } +} diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/DataInputOutputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/DataInputOutputStream.java new file mode 100644 index 0000000..be04b8c --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/stream/DataInputOutputStream.java @@ -0,0 +1,103 @@ +package it.cavallium.stream; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class DataInputOutputStream extends DataOutputStream implements DataInputOutput { + + private final DataInputStream in; + + public DataInputOutputStream(DataInputStream in, DataOutputStream out) { + super(out); + this.in = in; + } + + @Override + public DataInputStream getIn() { + return in; + } + + @Override + public DataOutputStream getOut() { + return this; + } + + @Override + public void readFully(byte @NotNull [] bytes) throws IOException { + in.readFully(bytes); + } + + @Override + public void readFully(byte @NotNull [] bytes, int i, int i1) throws IOException { + in.readFully(bytes, i, i1); + } + + @Override + public int skipBytes(int i) throws IOException { + return in.skipBytes(i); + } + + @Override + public boolean readBoolean() throws IOException { + return in.readBoolean(); + } + + @Override + public byte readByte() throws IOException { + return in.readByte(); + } + + @Override + public int readUnsignedByte() throws IOException { + return in.readUnsignedByte(); + } + + @Override + public short readShort() throws IOException { + return in.readShort(); + } + + @Override + public int readUnsignedShort() throws IOException { + return in.readUnsignedShort(); + } + + @Override + public char readChar() throws IOException { + return in.readChar(); + } + + @Override + public int readInt() throws IOException { + return in.readInt(); + } + + @Override + public long readLong() throws IOException { + return in.readLong(); + } + + @Override + public float readFloat() throws IOException { + return in.readFloat(); + } + + @Override + public double readDouble() throws IOException { + return in.readDouble(); + } + + @Deprecated + @Override + public String readLine() throws IOException { + return in.readLine(); + } + + @NotNull + @Override + public String readUTF() throws IOException { + return in.readUTF(); + } +} diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeByteArrayInputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeByteArrayInputStream.java new file mode 100644 index 0000000..b790b90 --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeByteArrayInputStream.java @@ -0,0 +1,136 @@ +/* + * 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; + +/** Simple, fast and repositionable byte-array input stream. + * + *

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: + *

+	 * read(b, 0, b.length)
+	 * 
+ * + * @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. + * + *

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 0)) { + total += cur; + } + + return total; + } + + /** + * See the general contract of the {@code readBoolean} + * method of {@code DataInput}. + *

+ * 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: + *

+	 *     DataInputStream d = new DataInputStream(in);
+	 * 
+ * with: + *
+	 *     BufferedReader d
+	 *          = new BufferedReader(new InputStreamReader(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}. + *

+ * 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: + *

{@code
+	 * (byte)(0xff & (v >> 8))
+	 * (byte)(0xff & v)
+	 * }

+ * 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: + *

{@code
+	 * (byte)(0xff & (v >> 8))
+	 * (byte)(0xff & v)
+	 * }

+ * 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: + *

{@code
+	 * (byte)(0xff & (v >> 24))
+	 * (byte)(0xff & (v >> 16))
+	 * (byte)(0xff & (v >>  8))
+	 * (byte)(0xff & v)
+	 * }

+ * 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: + *

{@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)
+	 * }

+ * 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: + *

{@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);
+	 * }

+ * 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 \u0001 through + * \u007f, it is represented + * by one byte: + *

(byte)c 

+ * If a character {@code c} is \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))
+	 * }

If a character + * {@code c} is in the range \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))
+	 * }

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 bufs = null; + byte[] result = null; + int total = 0; + int remaining = len; + int n; + do { + byte[] buf = new byte[Math.min(remaining, DEFAULT_BUFFER_SIZE)]; + int nread = 0; + + // read to EOF which may read more or less than buffer size + while ((n = read(buf, nread, + Math.min(buf.length - nread, remaining))) > 0) { + nread += n; + remaining -= n; + } + + if (nread > 0) { + if (MAX_BUFFER_SIZE - total < nread) { + throw new OutOfMemoryError("Required array size too large"); + } + total += nread; + if (result == null) { + result = buf; + } else { + if (bufs == null) { + bufs = new ArrayList<>(); + bufs.add(result); + } + bufs.add(buf); + } + } + // if the last call to read returned -1 or the number of bytes + // requested have been read then break + } while (n >= 0 && remaining > 0); + + if (bufs == null) { + if (result == null) { + return new byte[0]; + } + return result.length == total ? + result : Arrays.copyOf(result, total); + } + + result = new byte[total]; + int offset = 0; + remaining = total; + for (byte[] b : bufs) { + int count = Math.min(b.length, remaining); + System.arraycopy(b, 0, result, offset, count); + offset += count; + remaining -= count; + } + + return result; + } + + public int readNBytes(byte[] b, int off, int len) { + Objects.checkFromIndexSize(off, len, b.length); + + int n = 0; + while (n < len) { + int count = read(b, off + n, len - n); + if (count < 0) + break; + n += count; + } + return n; + } + + public long skip(long n) { + long remaining = n; + int nr; + + if (n <= 0) { + return 0; + } + + int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining); + byte[] skipBuffer = new byte[size]; + while (remaining > 0) { + nr = read(skipBuffer, 0, (int)Math.min(size, remaining)); + if (nr < 0) { + break; + } + remaining -= nr; + } + + return n - remaining; + } + + public void skipNBytes(long n) { + if (n > 0) { + long ns = skip(n); + if (ns >= 0 && ns < n) { // skipped too few bytes + // adjust number to skip + n -= ns; + // read until requested number skipped or EOS reached + while (n > 0 && read() != -1) { + n--; + } + // if not enough skipped, then EOFE + if (n != 0) { + throw new IndexOutOfBoundsException(); + } + } else if (ns != n) { // skipped negative or too many bytes + throw new IllegalArgumentException("Unable to skip exactly"); + } + } + } + + public int available() { + return 0; + } + + public void close() {} + + public void mark(int readlimit) {} + + public void reset() { + throw new UnsupportedOperationException("mark/reset not supported"); + } + + public boolean markSupported() { + return false; + } + + public long transferTo(OutputStream out) { + Objects.requireNonNull(out, "out"); + long transferred = 0; + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + int read; + while ((read = this.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) { + try { + out.write(buffer, 0, read); + } catch (IOException e) { + throw new IllegalStateException(e); + } + transferred += read; + } + return transferred; + } +} diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeMeasurableInputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeMeasurableInputStream.java new file mode 100644 index 0000000..a64d3c4 --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeMeasurableInputStream.java @@ -0,0 +1,28 @@ +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. + */ + + +import java.io.InputStream; + +/** An {@link InputStream} that implements also the {@link SafeMeasurableStream} interface. + * + * @since 5.0.4 + */ + +public abstract class SafeMeasurableInputStream extends SafeInputStream implements SafeMeasurableStream { +} diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeMeasurableOutputStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeMeasurableOutputStream.java new file mode 100644 index 0000000..d8ec576 --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeMeasurableOutputStream.java @@ -0,0 +1,27 @@ +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. + */ + +import java.io.OutputStream; + +/** An {@link OutputStream} that implements also the {@link SafeMeasurableStream} interface. + * + * @since 6.0.0 + */ + +public abstract class SafeMeasurableOutputStream extends SafeOutputStream implements SafeMeasurableStream { +} diff --git a/data-generator-runtime/src/main/java/it/cavallium/stream/SafeMeasurableStream.java b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeMeasurableStream.java new file mode 100644 index 0000000..0e639f7 --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/stream/SafeMeasurableStream.java @@ -0,0 +1,53 @@ +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. + */ + + +import it.unimi.dsi.fastutil.io.FastBufferedInputStream; +import it.unimi.dsi.fastutil.io.FastBufferedOutputStream; +import it.unimi.dsi.fastutil.io.MeasurableInputStream; + +/** An stream that provides eager access to its length, + * and keeps track of the current position (e.g., the number of bytes read so far, or the current + * position of the file pointer). + * + *

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 {