From 784c86456c2fd2b341d46655662d68ecc9e1249d Mon Sep 17 00:00:00 2001 From: Andrea Cavalli Date: Sat, 21 Jan 2023 01:15:05 +0100 Subject: [PATCH] Update generators --- .../data/generator/plugin/ComputedType.java | 4 + .../generator/plugin/ComputedTypeBase.java | 5 +- .../generator/plugin/ComputedTypeNative.java | 11 +- .../generator/plugin/ComputedTypeSuper.java | 4 +- .../data/generator/plugin/DataModel.java | 37 +- .../generator/plugin/SourcesGenerator.java | 12 + .../plugin/classgen/GenCurrentVersion.java | 4 +- .../plugin/classgen/GenDataBaseX.java | 11 +- .../plugin/classgen/GenDataSuperX.java | 9 +- .../plugin/classgen/GenIVersion.java | 17 - .../plugin/classgen/GenSerializerArrayX.java | 119 +++ .../plugin/classgen/GenSerializerBaseX.java | 124 ++++ .../classgen/GenSerializerNullableX.java | 118 +++ .../plugin/classgen/GenSerializerSuperX.java | 153 ++++ .../generator/plugin/classgen/GenVersion.java | 24 +- .../nativedata/ImmutableWrappedArrayList.java | 682 ++++++++++++++++++ 16 files changed, 1288 insertions(+), 46 deletions(-) create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerArrayX.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerBaseX.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerNullableX.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerSuperX.java create mode 100644 data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ImmutableWrappedArrayList.java diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedType.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedType.java index 4a24a66..539fd6e 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedType.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedType.java @@ -41,4 +41,8 @@ public sealed interface ComputedType permits VersionedComputedType, ComputedType */ Stream getDependents(); + default boolean isPrimitive() { + return false; + } + } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeBase.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeBase.java index 12b5c10..52a82d1 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeBase.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeBase.java @@ -1,10 +1,7 @@ package it.cavallium.data.generator.plugin; -import static it.cavallium.data.generator.plugin.DataModel.joinPackage; - import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; -import it.cavallium.data.generator.nativedata.Serializers; import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; import java.util.LinkedHashMap; import java.util.Objects; @@ -101,7 +98,7 @@ public final class ComputedTypeBase implements VersionedComputedType { } @Override - public TypeName getJTypeName(String basePackageName) { + public ClassName getJTypeName(String basePackageName) { return ClassName.get(getVersion().getDataPackage(basePackageName), type.type()); } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNative.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNative.java index 5cfb7cf..992dcf4 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNative.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNative.java @@ -1,7 +1,5 @@ package it.cavallium.data.generator.plugin; -import static it.cavallium.data.generator.plugin.DataModel.joinPackage; - import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; import it.cavallium.data.generator.nativedata.Int52Serializer; @@ -9,16 +7,21 @@ import it.cavallium.data.generator.nativedata.Serializers; import it.cavallium.data.generator.nativedata.StringSerializer; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.stream.Stream; public final class ComputedTypeNative implements ComputedType { + private static final Set PRIMITIVE_TYPES = Set.of("boolean", "short", "char", "int", "long", "float", "double", "byte"); + private final String type; private final ComputedTypeSupplier computedTypeSupplier; + private boolean primitive; public ComputedTypeNative(String type, ComputedTypeSupplier computedTypeSupplier) { this.type = type; this.computedTypeSupplier = computedTypeSupplier; + this.primitive = PRIMITIVE_TYPES.contains(type); } public String getName() { @@ -109,4 +112,8 @@ public final class ComputedTypeNative implements ComputedType { public String toString() { return type + " (native)"; } + + public boolean isPrimitive() { + return primitive; + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSuper.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSuper.java index 454a351..15bd54c 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSuper.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSuper.java @@ -1,7 +1,5 @@ package it.cavallium.data.generator.plugin; -import static it.cavallium.data.generator.plugin.DataModel.joinPackage; - import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; @@ -94,7 +92,7 @@ public final class ComputedTypeSuper implements VersionedComputedType { } @Override - public TypeName getJTypeName(String basePackageName) { + public ClassName getJTypeName(String basePackageName) { return ClassName.get(getVersion().getDataPackage(basePackageName), type.type()); } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/DataModel.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/DataModel.java index 49ee29a..7878835 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/DataModel.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/DataModel.java @@ -672,6 +672,16 @@ public class DataModel { .filter(t -> t instanceof ComputedTypeBase).map(t -> (ComputedTypeBase) t); } + public Stream getArrayTypesComputed(ComputedVersion version) { + return this.computedTypes.get(version.getVersion()).values().stream() + .filter(t -> t instanceof ComputedTypeArray).map(t -> (ComputedTypeArray) t); + } + + public Stream getNullableTypesComputed(ComputedVersion version) { + return this.computedTypes.get(version.getVersion()).values().stream() + .filter(t -> t instanceof ComputedTypeNullable).map(t -> (ComputedTypeNullable) t); + } + public Optional getNextVersion(ComputedVersion versionConfiguration) { var nextVersion = versions.get(versionConfiguration.getVersion() + 1); return Optional.ofNullable(nextVersion); @@ -743,7 +753,7 @@ public class DataModel { if (prevVersion != null) { return versions.get(prevVersion.getVersion().getVersion() + 1); } else { - return type.getVersion(); + return versions.get(0); } } @@ -800,8 +810,29 @@ public class DataModel { } } - public Stream getSuperTypesOf(VersionedComputedType baseType) { - return getSuperTypesComputed(baseType.getVersion()).filter(type -> type.subTypes().contains(baseType)); + /** + * @param includeMulti Includes all used super type versions + */ + public Stream getSuperTypesOf(VersionedComputedType baseType, boolean includeMulti) { + ComputedVersion lowestBaseVersion = getTypeFirstSameVersion(baseType); + if (lowestBaseVersion == null) { + return Stream.of(); + } + return getSuperTypesComputed(baseType.getVersion()) + .filter(type -> type.subTypes().contains(baseType)) + .mapMulti((superType, cons) -> { + if (includeMulti) { + while (superType != null) { + ComputedVersion lowestSuperVersion = Objects.requireNonNull(getTypeFirstSameVersion(superType)); + if (lowestSuperVersion.compareTo(lowestBaseVersion) >= 0) { + cons.accept(superType); + } + superType = getPrevVersion(superType); + } + } else { + cons.accept(superType); + } + }); } public Stream getExtendsInterfaces(ComputedTypeSuper superType) { 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 25d990a..279b4f3 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 @@ -24,6 +24,10 @@ import it.cavallium.data.generator.plugin.classgen.GenINullableSuperType; import it.cavallium.data.generator.plugin.classgen.GenIType; import it.cavallium.data.generator.plugin.classgen.GenIVersion; import it.cavallium.data.generator.plugin.classgen.GenNullableX; +import it.cavallium.data.generator.plugin.classgen.GenSerializerArrayX; +import it.cavallium.data.generator.plugin.classgen.GenSerializerBaseX; +import it.cavallium.data.generator.plugin.classgen.GenSerializerNullableX; +import it.cavallium.data.generator.plugin.classgen.GenSerializerSuperX; import it.cavallium.data.generator.plugin.classgen.GenSuperType; import it.cavallium.data.generator.plugin.classgen.GenVersion; import it.cavallium.data.generator.plugin.classgen.GenVersions; @@ -175,6 +179,14 @@ public class SourcesGenerator { new GenDataSuperX(genParams).run(); + new GenSerializerSuperX(genParams).run(); + + new GenSerializerBaseX(genParams).run(); + + new GenSerializerArrayX(genParams).run(); + + new GenSerializerNullableX(genParams).run(); + // Update the hash at the end Files.writeString(hashPath, basePackageName + '\n' + useRecordBuilders + '\n' + curHash + '\n', StandardCharsets.UTF_8, TRUNCATE_EXISTING, WRITE, CREATE); 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 df5e7ec..64eebb7 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 @@ -156,7 +156,7 @@ public class GenCurrentVersion extends ClassGenerator { .addStatement( "data = " + versionConfiguration.getPackage(basePackageName) + ".Version.upgradeToNextVersion(($T) data)", - ClassName.get(versionConfiguration.getDataPackage(basePackageName), "IBaseType") + ClassName.get(versionConfiguration.getPackage(basePackageName), "IBaseType") ); } } @@ -178,7 +178,7 @@ public class GenCurrentVersion extends ClassGenerator { var baseTypeClassName = ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"); methodBuilder.addParameter(baseTypeClassName, "type"); - var iBaseTypeClassName = ClassName.get(version.getDataPackage(basePackageName), "IBaseType"); + var iBaseTypeClassName = ClassName.get(version.getPackage(basePackageName), "IBaseType"); methodBuilder.returns(ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(iBaseTypeClassName))); methodBuilder.beginControlFlow("return switch (type)"); diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataBaseX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataBaseX.java index 8c8089d..c8281b9 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataBaseX.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataBaseX.java @@ -38,12 +38,17 @@ public class GenDataBaseX extends ClassGenerator { classBuilder.addModifiers(Modifier.PUBLIC); var baseTypeClass = ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"); - var iTypeClass = ClassName.get(version.getPackage(basePackageName), "IBaseType"); - classBuilder.addSuperinterface(iTypeClass); - dataModel.getSuperTypesOf(base).forEach(superType -> { + dataModel.getTypeSameVersions(base).forEach(v -> { + var iTypeClass = ClassName.get(v.getPackage(basePackageName), "IBaseType"); + classBuilder.addSuperinterface(iTypeClass); + }); + + dataModel.getSuperTypesOf(base, true).forEach(superType -> { classBuilder.addSuperinterface(superType.getJTypeName(basePackageName)); + }); + dataModel.getSuperTypesOf(base, false).forEach(superType -> { classBuilder.addMethod(MethodSpec .methodBuilder("getMetaId$" + superType.getName()) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataSuperX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataSuperX.java index 72da73a..63f1bfb 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataSuperX.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataSuperX.java @@ -36,9 +36,10 @@ public class GenDataSuperX extends ClassGenerator { classBuilder.addModifiers(Modifier.PUBLIC); - var baseTypeClass = ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"); - var iBaseTypeClass = ClassName.get(version.getPackage(basePackageName), "IBaseType"); - classBuilder.addSuperinterface(iBaseTypeClass); + dataModel.getTypeSameVersions(typeSuper).forEach(v -> { + var iTypeClass = ClassName.get(v.getPackage(basePackageName), "IBaseType"); + classBuilder.addSuperinterface(iTypeClass); + }); dataModel.getExtendsInterfaces(typeSuper).forEach(superType -> { classBuilder.addSuperinterface(superType.getJTypeName(basePackageName)); @@ -70,7 +71,7 @@ public class GenDataSuperX extends ClassGenerator { classBuilder.addMethod(setter.build()); }); - dataModel.getSuperTypesOf(typeSuper).forEach(superType -> { + dataModel.getSuperTypesOf(typeSuper, true).forEach(superType -> { classBuilder.addSuperinterface(superType.getJTypeName(basePackageName)); }); diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIVersion.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIVersion.java index 94be0ab..26ea338 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIVersion.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIVersion.java @@ -7,10 +7,8 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; -import com.squareup.javapoet.WildcardTypeName; import it.cavallium.data.generator.DataSerializer; import it.cavallium.data.generator.plugin.ClassGenerator; -import it.cavallium.data.generator.plugin.ClassGenerator.ClassGeneratorParams; import java.io.IOException; import java.util.stream.Stream; import javax.lang.model.element.Modifier; @@ -27,21 +25,6 @@ public class GenIVersion extends ClassGenerator { iVersionClass.addModifiers(Modifier.PUBLIC); iVersionClass.addTypeVariable(TypeVariableName.get("B")); - // Add getClass method - { - var getClassMethodBuilder = MethodSpec - .methodBuilder("getClass") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.ABSTRACT) - .returns(ParameterizedTypeName.get(ClassName.get(Class.class), - WildcardTypeName.subtypeOf(TypeVariableName.get("B")) - )) - .addParameter(ParameterSpec - .builder(ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"), "type") - .build()); - iVersionClass.addMethod(getClassMethodBuilder.build()); - } - // Add getSerializer method { var getSerializerMethodBuilder = MethodSpec 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 new file mode 100644 index 0000000..70b82b3 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerArrayX.java @@ -0,0 +1,119 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeSpec.Builder; +import it.cavallium.data.generator.DataSerializer; +import it.cavallium.data.generator.nativedata.ImmutableWrappedArrayList; +import it.cavallium.data.generator.plugin.ClassGenerator; +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 java.io.NotSerializableException; +import java.util.Objects; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; +import org.jetbrains.annotations.NotNull; + +public class GenSerializerArrayX extends ClassGenerator { + + public GenSerializerArrayX(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().parallelStream().flatMap(this::generateVersionClasses); + } + + private Stream generateVersionClasses(ComputedVersion version) { + return dataModel + .getArrayTypesComputed(version) + .filter(type -> type instanceof ComputedTypeArrayVersioned versioned && versioned.getVersion().equals(version)) + .map(type -> generateTypeVersioned(version, (ComputedTypeArrayVersioned) type)); + } + + private GeneratedClass generateTypeVersioned(ComputedVersion version, ComputedTypeArrayVersioned typeArray) { + ClassName serializerClassName = typeArray.getJSerializerName(basePackageName); + var typeArrayClassName = typeArray.getJTypeName(basePackageName); + + var classBuilder = TypeSpec.classBuilder(serializerClassName.simpleName()); + + classBuilder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + classBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), typeArrayClassName)); + + generateSerialize(version, typeArray, classBuilder); + + generateDeserialize(version, typeArray, classBuilder); + + return new GeneratedClass(serializerClassName.packageName(), classBuilder); + } + + private void generateSerialize(ComputedVersion version, ComputedTypeArrayVersioned typeArray, Builder classBuilder) { + 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(typeArray.getJTypeName(basePackageName), "data") + .addAnnotation(NotNull.class) + .build()); + + if (version.isCurrent()) { + method.addStatement("$T.requireNonNull(data)", Objects.class); + method.addCode("\n"); + method.addStatement("final int sz = data.size()"); + method.addStatement("out.writeInt(sz)"); + method.addCode("\n"); + method.beginControlFlow("for (int i = 0; i < sz; ++i)"); + var baseSerializerInstance = typeArray.getBase().getJSerializerInstance(basePackageName); + method.addStatement("$T.$N.serialize(out, ($T) data.get(i))", + baseSerializerInstance.className(), + baseSerializerInstance.fieldName(), + typeArray.getBase().getJTypeName(basePackageName) + ); + method.endControlFlow(); + } else { + method.addStatement("throw new $T()", NotSerializableException.class); + } + + classBuilder.addMethod(method.build()); + } + + private void generateDeserialize(ComputedVersion version, ComputedTypeArrayVersioned typeArray, Builder classBuilder) { + 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.addStatement("int sz = in.readInt()"); + var arrayTypeName = ArrayTypeName.of(typeArray.getBase().getJTypeName(basePackageName)); + method.addStatement("$T a = new $T[sz]", arrayTypeName, arrayTypeName.componentType); + method.addCode("\n"); + method.beginControlFlow("for (int i = 0; i < sz; ++i)"); + var baseSerializerInstance = typeArray.getBase().getJSerializerInstance(basePackageName); + method.addStatement("a[i] = $T.$N.deserialize(in)", baseSerializerInstance.className(), baseSerializerInstance.fieldName()); + method.endControlFlow(); + + method.addCode("\n"); + method.addStatement("return new $T(a)", ParameterizedTypeName.get(ClassName.get(ImmutableWrappedArrayList.class), + typeArray.getBase().getJTypeName(basePackageName))); + + classBuilder.addMethod(method.build()); + } +} 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 new file mode 100644 index 0000000..fd36b14 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerBaseX.java @@ -0,0 +1,124 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeSpec.Builder; +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 java.io.NotSerializableException; +import java.util.Objects; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; + +public class GenSerializerBaseX extends ClassGenerator { + + public GenSerializerBaseX(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().parallelStream().flatMap(this::generateVersionClasses); + } + + private Stream generateVersionClasses(ComputedVersion version) { + return dataModel + .getBaseTypesComputed(version) + .filter(type -> type.getVersion().equals(version)) + .map(type -> generateTypeVersioned(version, type)); + } + + private GeneratedClass generateTypeVersioned(ComputedVersion version, ComputedTypeBase typeBase) { + ClassName serializerClassName = typeBase.getJSerializerName(basePackageName); + ClassName typeBaseClassName = typeBase.getJTypeName(basePackageName); + + var classBuilder = TypeSpec.classBuilder(serializerClassName.simpleName()); + + classBuilder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + classBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), typeBaseClassName)); + + generateSerialize(version, typeBase, classBuilder); + + generateDeserialize(version, typeBase, classBuilder); + + return new GeneratedClass(serializerClassName.packageName(), classBuilder); + } + + private void generateSerialize(ComputedVersion version, ComputedTypeBase typeBase, Builder classBuilder) { + 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(typeBase.getJTypeName(basePackageName), "data") + .addAnnotation(NotNull.class) + .build()); + + if (version.isCurrent()) { + method.addStatement("$T.requireNonNull(data)", Objects.class); + method.addCode("\n"); + + typeBase.getData().forEach((fieldName, fieldType) -> { + if (fieldType.isPrimitive()) { + method.addStatement("out.write$N(data.$N())", StringUtils.capitalize(fieldType.getName()), fieldName); + } else { + var serializerInstance = fieldType.getJSerializerInstance(basePackageName); + method.addStatement("$T.$N.serialize(out, data.$N())", + serializerInstance.className(), + serializerInstance.fieldName(), + fieldName + ); + } + }); + } else { + method.addStatement("throw new $T()", NotSerializableException.class); + } + + classBuilder.addMethod(method.build()); + } + + private void generateDeserialize(ComputedVersion version, ComputedTypeBase typeBase, Builder classBuilder) { + 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.addCode("return new $T(\n$>", typeBaseClassName); + typeBase.getData().entrySet().stream().flatMap(entry -> { + var fieldType = entry.getValue(); + if (fieldType.isPrimitive()) { + return Stream.of(CodeBlock.of(",\n"), CodeBlock.of("in.read$N()", StringUtils.capitalize(fieldType.getName()))); + } else { + var serializerInstance = fieldType.getJSerializerInstance(basePackageName); + return Stream.of(CodeBlock.of(",\n"), CodeBlock.of("$T.$N.deserialize(in)", + serializerInstance.className(), + serializerInstance.fieldName() + )); + } + }).skip(1).forEach(method::addCode); + method.addCode("$<\n"); + method.addStatement(")"); + + classBuilder.addMethod(method.build()); + } +} 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 new file mode 100644 index 0000000..094b094 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerNullableX.java @@ -0,0 +1,118 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeSpec.Builder; +import it.cavallium.data.generator.DataSerializer; +import it.cavallium.data.generator.plugin.ClassGenerator; +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 java.io.NotSerializableException; +import java.util.Objects; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; +import org.jetbrains.annotations.NotNull; + +public class GenSerializerNullableX extends ClassGenerator { + + public GenSerializerNullableX(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().parallelStream().flatMap(this::generateVersionClasses); + } + + private Stream generateVersionClasses(ComputedVersion version) { + return dataModel + .getNullableTypesComputed(version) + .filter(type -> type instanceof ComputedTypeNullableVersioned versioned && versioned.getVersion().equals(version)) + .map(type -> generateTypeVersioned(version, (ComputedTypeNullableVersioned) type)); + } + + private GeneratedClass generateTypeVersioned(ComputedVersion version, ComputedTypeNullableVersioned typeNullable) { + ClassName serializerClassName = typeNullable.getJSerializerName(basePackageName); + var typeNullableClassName = typeNullable.getJTypeName(basePackageName); + + var classBuilder = TypeSpec.classBuilder(serializerClassName.simpleName()); + + classBuilder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + classBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), typeNullableClassName)); + + generateSerialize(version, typeNullable, classBuilder); + + generateDeserialize(version, typeNullable, classBuilder); + + return new GeneratedClass(serializerClassName.packageName(), classBuilder); + } + + private void generateSerialize(ComputedVersion version, ComputedTypeNullableVersioned typeNullable, Builder classBuilder) { + var method = MethodSpec.methodBuilder("serialize"); + + var base = typeNullable.getBase(); + var baseTypeName = base.getJTypeName(basePackageName); + 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(typeNullable.getJTypeName(basePackageName), "data") + .addAnnotation(NotNull.class) + .build()); + + if (version.isCurrent()) { + method.addStatement("$T.requireNonNull(data)", Objects.class); + method.addCode("\n"); + method.addStatement("boolean notEmpty = !data.isEmpty()"); + method.addStatement("out.writeBoolean(notEmpty)"); + method.beginControlFlow("if (notEmpty)"); + method.addStatement("$T.$N.serialize(out, ($T) data.getNullable())", + baseSerializerInstance.className(), + baseSerializerInstance.fieldName(), + baseTypeName + ); + method.endControlFlow(); + } else { + method.addStatement("throw new $T()", NotSerializableException.class); + } + + classBuilder.addMethod(method.build()); + } + + private void generateDeserialize(ComputedVersion version, ComputedTypeNullableVersioned typeNullable, Builder classBuilder) { + var method = MethodSpec.methodBuilder("deserialize"); + + var base = typeNullable.getBase(); + var baseTypeName = base.getJTypeName(basePackageName); + 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.addStatement("return in.readBoolean() ? new $T(($T) $T.$N.deserialize(in)) : $T.empty()", + typeNullableClassName, + baseTypeName, + baseSerializerInstance.className(), + baseSerializerInstance.fieldName(), + typeNullableClassName + ); + + classBuilder.addMethod(method.build()); + } +} 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 new file mode 100644 index 0000000..612c121 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSerializerSuperX.java @@ -0,0 +1,153 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeSpec.Builder; +import it.cavallium.data.generator.DataSerializer; +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 java.io.IOException; +import java.io.NotSerializableException; +import java.util.Objects; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; +import org.jetbrains.annotations.NotNull; + +public class GenSerializerSuperX extends ClassGenerator { + + public GenSerializerSuperX(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().parallelStream().flatMap(this::generateVersionClasses); + } + + private Stream generateVersionClasses(ComputedVersion version) { + return dataModel + .getSuperTypesComputed(version) + .filter(type -> type.getVersion().equals(version)) + .map(type -> generateTypeVersioned(version, type)); + } + + private GeneratedClass generateTypeVersioned(ComputedVersion version, ComputedTypeSuper typeSuper) { + ClassName serializerClassName = typeSuper.getJSerializerName(basePackageName); + ClassName typeSuperClassName = typeSuper.getJTypeName(basePackageName); + + var classBuilder = TypeSpec.classBuilder(serializerClassName.simpleName()); + + classBuilder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + classBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), typeSuperClassName)); + + generateCheckIdValidity(version, typeSuper, classBuilder); + + generateSerialize(version, typeSuper, classBuilder); + + generateDeserialize(version, typeSuper, classBuilder); + + return new GeneratedClass(serializerClassName.packageName(), classBuilder); + } + + private void generateCheckIdValidity(ComputedVersion version, ComputedTypeSuper typeSuper, Builder classBuilder) { + 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.endControlFlow(); + + classBuilder.addMethod(method.build()); + } + + private void generateSerialize(ComputedVersion version, ComputedTypeSuper typeSuper, Builder classBuilder) { + 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(typeSuper.getJTypeName(basePackageName), "data") + .addAnnotation(NotNull.class) + .build()); + + if (version.isCurrent()) { + method.addStatement("$T.requireNonNull(data)", Objects.class); + method.addStatement("int id = data.getMetaId$$$N()", typeSuper.getName()); + method.addStatement("out.writeByte(id)"); + method.beginControlFlow("switch (id)"); + + var subTypes = typeSuper.subTypes().toArray(ComputedType[]::new); + int max = subTypes.length; + for (int i = 0; i < max; i++) { + var subType = subTypes[i]; + var subSerializerInstance = subType.getJSerializerInstance(basePackageName); + method.addStatement("case $L -> $T.$N.serialize(out, ($T) data)", + i, + subSerializerInstance.className(), + subSerializerInstance.fieldName(), + subType.getJTypeName(basePackageName) + ); + } + method.beginControlFlow("default ->"); + method.addStatement("checkIdValidity(id)"); + method.addComment("Not reachable:"); + method.addStatement("throw new $T()", IllegalStateException.class); + method.endControlFlow(); + method.endControlFlow(); + } else { + method.addStatement("throw new $T()", NotSerializableException.class); + } + + classBuilder.addMethod(method.build()); + } + + private void generateDeserialize(ComputedVersion version, ComputedTypeSuper typeSuper, Builder classBuilder) { + 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.addStatement("int id = in.readUnsignedByte()"); + method.beginControlFlow("return switch (id)"); + + var subTypes = typeSuper.subTypes().toArray(ComputedType[]::new); + int max = subTypes.length; + for (int i = 0; i < max; i++) { + var subType = subTypes[i]; + var subSerializerInstance = subType.getJSerializerInstance(basePackageName); + method.addStatement("case $L -> ($T) $T.$N.deserialize(in)", + i, + subType.getJTypeName(basePackageName), + subSerializerInstance.className(), + subSerializerInstance.fieldName() + ); + } + method.beginControlFlow("default ->"); + method.addStatement("checkIdValidity(id)"); + method.addComment("Not reachable:"); + method.addStatement("throw new $T()", IllegalStateException.class); + method.endControlFlow(); + method.addCode("$<};"); + + classBuilder.addMethod(method.build()); + } +} 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 d4b1797..29fd577 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 @@ -10,11 +10,13 @@ import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeSpec.Builder; import com.squareup.javapoet.TypeVariableName; import it.cavallium.data.generator.DataSerializer; +import it.cavallium.data.generator.DataUpgrader; import it.cavallium.data.generator.plugin.ClassGenerator; import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; import it.cavallium.data.generator.plugin.ComputedTypeCustom; 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; @@ -37,7 +39,7 @@ public class GenVersion extends ClassGenerator { classBuilder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); var iVersionClassName = ClassName.get(dataModel.getRootPackage(basePackageName), "IVersion"); - var iBaseTypeClassName = ClassName.get(version.getDataPackage(basePackageName), "IBaseType"); + var iBaseTypeClassName = ClassName.get(version.getPackage(basePackageName), "IBaseType"); classBuilder.addSuperinterface(ParameterizedTypeName.get(iVersionClassName, iBaseTypeClassName)); generateVersionField(version, classBuilder); @@ -101,10 +103,10 @@ public class GenVersion extends ClassGenerator { methodBuilder.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); methodBuilder.addException(ClassName.get(IOException.class)); - var nextIBaseType = ClassName.get(nextVersion.getDataPackage(basePackageName), "IBaseType"); + var nextIBaseType = ClassName.get(nextVersion.getPackage(basePackageName), "IBaseType"); methodBuilder.returns(nextIBaseType); - var iBaseTypeClassName = ClassName.get(version.getDataPackage(basePackageName), "IBaseType"); + var iBaseTypeClassName = ClassName.get(version.getPackage(basePackageName), "IBaseType"); methodBuilder.addParameter(iBaseTypeClassName, "oldData"); methodBuilder.beginControlFlow( "return switch (oldData.getBaseType$$())"); @@ -169,19 +171,24 @@ public class GenVersion extends ClassGenerator { var versionClassType = ClassName.get(version.getPackage(basePackageName), "Version"); dataModel.getComputedTypes(version).forEach((typeName, type) -> { boolean shouldCreateInstanceField = type instanceof VersionedComputedType versionedComputedType - && versionedComputedType.getVersion().equals(version); + && versionedComputedType.getVersion().equals(version) && !version.isCurrent(); if (!shouldCreateInstanceField) { return; } + var nextVersion = Objects.requireNonNull(dataModel.getNextVersion(type)); + var upgraderFieldLocation = type.getJUpgraderInstance(basePackageName); if (!versionClassType.equals(upgraderFieldLocation.className())) { return; } + var genericClassName = ParameterizedTypeName.get(ClassName.get(DataUpgrader.class), + type.getJTypeName(basePackageName), nextVersion.getJTypeName(basePackageName) + ); var upgraderClassName = type.getJUpgraderName(basePackageName); - var fieldBuilder = FieldSpec.builder(upgraderClassName, + var fieldBuilder = FieldSpec.builder(genericClassName, upgraderFieldLocation.fieldName(), Modifier.PUBLIC, Modifier.STATIC, @@ -199,15 +206,16 @@ public class GenVersion extends ClassGenerator { methodBuilder.addAnnotation(Override.class); methodBuilder.addException(ClassName.get(IOException.class)); - var iBaseTypeClassName = ClassName.get(version.getDataPackage(basePackageName), "IBaseType"); + var iBaseTypeClassName = ClassName.get(version.getPackage(basePackageName), "IBaseType"); methodBuilder.addTypeVariable(TypeVariableName.get("T", iBaseTypeClassName)); var baseTypeClassName = ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"); methodBuilder.addParameter(baseTypeClassName, "type"); - methodBuilder.returns(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), TypeVariableName.get("T"))); + var returnType = ParameterizedTypeName.get(ClassName.get(DataSerializer.class), TypeVariableName.get("T")); + methodBuilder.returns(returnType); - methodBuilder.beginControlFlow("return switch (type)"); + methodBuilder.beginControlFlow("return ($T) switch (type)", returnType); dataModel.getBaseTypesComputed(version).forEach(baseType -> { var field = baseType.getJSerializerInstance(basePackageName); methodBuilder.addStatement("case $N -> $T.$N", baseType.getName(), field.className(), field.fieldName()); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ImmutableWrappedArrayList.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ImmutableWrappedArrayList.java new file mode 100644 index 0000000..ae81dba --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/ImmutableWrappedArrayList.java @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2002-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.data.generator.nativedata; + +import it.unimi.dsi.fastutil.objects.AbstractObjectList; +import it.unimi.dsi.fastutil.objects.ObjectArrays; +import it.unimi.dsi.fastutil.objects.ObjectIterators; +import it.unimi.dsi.fastutil.objects.ObjectList; +import it.unimi.dsi.fastutil.objects.ObjectListIterator; +import it.unimi.dsi.fastutil.objects.ObjectSpliterator; +import it.unimi.dsi.fastutil.objects.ObjectSpliterators; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.NoSuchElementException; +import java.util.RandomAccess; +import java.util.function.Consumer; +import org.jetbrains.annotations.NotNull; + +public class ImmutableWrappedArrayList extends AbstractObjectList implements RandomAccess { + /** The backing array. */ + protected final K[] a; + /** The current actual size of the list (never greater than the backing-array length). */ + protected final int size; + + /** + * Creates a new array list and fills it with the elements of a given array. + * + * @param a an array whose elements will be used to fill the array list. + */ + public ImmutableWrappedArrayList(final K[] a) { + this.a = a; + this.size = a.length; + } + + /** + * 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. + */ + @SafeVarargs + public static ImmutableWrappedArrayList of(final K... init) { + return new ImmutableWrappedArrayList<>(init); + } + + private UnsupportedOperationException ex() { + return new UnsupportedOperationException("Immutable"); + } + + @Override + public void add(final int index, final K k) { + throw ex(); + } + + @Override + public boolean add(final K k) { + throw ex(); + } + + @Override + public K get(final int index) { + if (index >= size) throw new IndexOutOfBoundsException("Index (" + index + ") is greater than or equal to list size (" + size + ")"); + return a[index]; + } + + @Override + public int indexOf(final Object k) { + for (int i = 0; i < size; i++) if (java.util.Objects.equals(k, a[i])) return i; + return -1; + } + + @Override + public int lastIndexOf(final Object k) { + for (int i = size; i-- != 0;) if (java.util.Objects.equals(k, a[i])) return i; + return -1; + } + + @Override + public K remove(final int index) { + throw ex(); + } + + @Override + public boolean remove(final Object k) { + throw ex(); + } + + @Override + public K set(final int index, final K k) { + throw ex(); + } + + @Override + public void clear() { + throw ex(); + } + + @Override + public int size() { + return size; + } + + @Override + public void size(final int size) { + throw ex(); + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + private class SubList extends AbstractObjectList.ObjectRandomAccessSubList { + + protected SubList(int from, int to) { + super(ImmutableWrappedArrayList.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 K[] getParentArray() { + return a; + } + + @Override + public K get(int i) { + ensureRestrictedIndex(i); + return a[i + from]; + } + + private final class SubListIterator extends ObjectIterators.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 K get(int i) { + return a[from + i]; + } + + @Override + protected void add(int i, K k) { + SubList.this.add(i, k); + } + + @Override + protected void set(int i, K k) { + SubList.this.set(i, k); + } + + @Override + protected void remove(int i) { + SubList.this.remove(i); + } + + @Override + protected int getMaxPos() { + return to - from; + } + + @Override + public K next() { + if (!hasNext()) throw new NoSuchElementException(); + return a[from + (lastReturned = pos++)]; + } + + @Override + public K previous() { + if (!hasPrevious()) throw new NoSuchElementException(); + return a[from + (lastReturned = --pos)]; + } + + @Override + public void forEachRemaining(final Consumer action) { + final int max = to - from; + while (pos < max) { + action.accept(a[from + (lastReturned = pos++)]); + } + } + } + + @Override + public @NotNull ObjectListIterator listIterator(int index) { + return new SubListIterator(index); + } + + private final class SubListSpliterator extends ObjectSpliterators.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 K get(int i) { + return a[i]; + } + + @Override + protected SubListSpliterator makeForSplit(int pos, int maxPos) { + return new SubListSpliterator(pos, maxPos); + } + + @Override + public boolean tryAdvance(final Consumer action) { + if (pos >= getMaxPos()) return false; + action.accept(a[pos++]); + return true; + } + + @Override + public void forEachRemaining(final Consumer action) { + final int max = getMaxPos(); + while (pos < max) { + action.accept(a[pos++]); + } + } + } + + @Override + public ObjectSpliterator spliterator() { + return new SubListSpliterator(); + } + + @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 ImmutableWrappedArrayList) { + @SuppressWarnings("unchecked") ImmutableWrappedArrayList other = (ImmutableWrappedArrayList)o; + return Arrays.equals(a, from, to, other.a, 0, other.size()); + } + if (o instanceof ImmutableWrappedArrayList.SubList) { + @SuppressWarnings("unchecked") + ImmutableWrappedArrayList.SubList other = (ImmutableWrappedArrayList.SubList)o; + return Arrays.equals(a, from, to, other.getParentArray(), other.from, other.to); + } + return super.equals(o); + } + + @SuppressWarnings("unchecked") + int contentsCompareTo(K[] otherA, int otherAFrom, int otherATo) { + return Arrays.compare(a, from, to, otherA, otherAFrom, otherATo, (o1, o2) -> ((Comparable)o1).compareTo(o2)); + } + + @SuppressWarnings("unchecked") + @Override + public int compareTo(final java.util.List l) { + if (l instanceof ImmutableWrappedArrayList) { + @SuppressWarnings("unchecked") ImmutableWrappedArrayList other = (ImmutableWrappedArrayList)l; + return contentsCompareTo(other.a, 0, other.size()); + } + if (l instanceof ImmutableWrappedArrayList.SubList) { + @SuppressWarnings("unchecked") + ImmutableWrappedArrayList.SubList other = (ImmutableWrappedArrayList.SubList)l; + return contentsCompareTo(other.getParentArray(), other.from, other.to); + } + return super.compareTo(l); + } + // We don't override subList as we want AbstractList's "sub-sublist" nesting handling, + // which would be tricky to do here. + // TODO Do override it so array access isn't sent through N indirections. + // This will likely mean making this class static. + } + + @Override + public ObjectList 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); + } + + /** + * Copies element of this type-specific list into the given array using optimized system calls. + * + * @param from the start index (inclusive). + * @param a the destination array. + * @param offset the offset into the destination array where to store the first element copied. + * @param length the number of elements to be copied. + */ + @Override + public void getElements(final int from, final Object[] a, final int offset, final int length) { + ObjectArrays.ensureOffsetLength(a, offset, length); + System.arraycopy(this.a, from, a, offset, length); + } + + /** + * Removes elements of this type-specific list using optimized system calls. + * + * @param from the start index (inclusive). + * @param to the end index (exclusive). + */ + @Override + public void removeElements(final int from, final int to) { + throw ex(); + } + + /** + * Adds elements to this type-specific list using optimized system calls. + * + * @param index the index at which to add elements. + * @param a the array containing the elements. + * @param offset the offset of the first element to add. + * @param length the number of elements to add. + */ + @Override + public void addElements(final int index, final K[] a, final int offset, final int length) { + throw ex(); + } + + /** + * Sets elements to this type-specific list using optimized system calls. + * + * @param index the index at which to start setting elements. + * @param a the array containing the elements. + * @param offset the offset of the first element to add. + * @param length the number of elements to add. + */ + @Override + public void setElements(final int index, final K[] a, final int offset, final int length) { + ensureIndex(index); + ObjectArrays.ensureOffsetLength(a, offset, length); + if (index + length > size) throw new IndexOutOfBoundsException("End index (" + (index + length) + ") is greater than list size (" + size + ")"); + System.arraycopy(a, offset, this.a, index, length); + } + + @Override + public void forEach(final Consumer action) { + for (int i = 0; i < size; ++i) { + action.accept(a[i]); + } + } + + @Override + public boolean addAll(int index, final Collection c) { + throw ex(); + } + + @Override + public boolean addAll(final int index, final ObjectList l) { + throw ex(); + } + + @Override + public boolean removeAll(final @NotNull Collection c) { + throw ex(); + } + + @Override + public Object[] toArray() { + final int size = size(); + // A subtle part of the spec says the returned array must be Object[] exactly. + if (size == 0) return it.unimi.dsi.fastutil.objects.ObjectArrays.EMPTY_ARRAY; + return Arrays.copyOf(a, size, Object[].class); + } + + @SuppressWarnings("unchecked") + @Override + public K2[] toArray(K2[] a) { + if (a == null) { + a = (K2[])new Object[size()]; + } else if (a.length < size()) { + a = (K2[])Array.newInstance(a.getClass().getComponentType(), size()); + } + //noinspection ReassignedVariable,SuspiciousSystemArraycopy + System.arraycopy(this.a, 0, a, 0, size()); + if (a.length > size()) { + a[size()] = null; + } + return a; + } + + @Override + public ObjectListIterator listIterator(final int index) { + ensureIndex(index); + return new ObjectListIterator<>() { + int pos = index, last = -1; + + @Override + public boolean hasNext() { + return pos < size; + } + + @Override + public boolean hasPrevious() { + return pos > 0; + } + + @Override + public K next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return a[last = pos++]; + } + + @Override + public K previous() { + if (!hasPrevious()) { + throw new NoSuchElementException(); + } + return a[last = --pos]; + } + + @Override + public int nextIndex() { + return pos; + } + + @Override + public int previousIndex() { + return pos - 1; + } + + @Override + public void add(K k) { + ImmutableWrappedArrayList.this.add(pos++, k); + last = -1; + } + + @Override + public void set(K k) { + if (last == -1) { + throw new IllegalStateException(); + } + ImmutableWrappedArrayList.this.set(last, k); + } + + @Override + public void remove() { + if (last == -1) { + throw new IllegalStateException(); + } + ImmutableWrappedArrayList.this.remove(last); + /* If the last operation was a next(), we are removing an element *before* us, and we must decrease pos correspondingly. */ + if (last < pos) { + pos--; + } + last = -1; + } + + @Override + public void forEachRemaining(final Consumer action) { + while (pos < size) { + action.accept(a[last = pos++]); + } + } + + @Override + public int back(int n) { + if (n < 0) { + throw new IllegalArgumentException("Argument must be nonnegative: " + n); + } + final int remaining = size - pos; + if (n < remaining) { + pos -= n; + } else { + n = remaining; + pos = 0; + } + last = pos; + return n; + } + + @Override + public int skip(int n) { + if (n < 0) { + throw new IllegalArgumentException("Argument must be nonnegative: " + n); + } + final int remaining = size - pos; + if (n < remaining) { + pos += n; + } else { + n = remaining; + pos = size; + } + last = pos - 1; + return n; + } + }; + } + + // If you update this, you will probably want to update ArraySet as well + private final class Spliterator implements ObjectSpliterator { + // Until we split, we will track the size of the list. + // Once we split, then we stop updating on structural modifications. + // Aka, size is late-binding. + boolean hasSplit; + int pos, max; + + public Spliterator() { + this(0, ImmutableWrappedArrayList.this.size, false); + } + + private Spliterator(int pos, int max, boolean hasSplit) { + assert pos <= max : "pos " + pos + " must be <= max " + max; + this.pos = pos; + this.max = max; + this.hasSplit = hasSplit; + } + + private int getWorkingMax() { + return hasSplit ? max : ImmutableWrappedArrayList.this.size; + } + + @Override + public int characteristics() { + return ObjectSpliterators.LIST_SPLITERATOR_CHARACTERISTICS; + } + + @Override + public long estimateSize() { + return getWorkingMax() - pos; + } + + @Override + public boolean tryAdvance(final Consumer action) { + if (pos >= getWorkingMax()) return false; + action.accept(a[pos++]); + return true; + } + + @Override + public void forEachRemaining(final Consumer action) { + for (final int max = getWorkingMax(); pos < max; ++pos) { + action.accept(a[pos]); + } + } + + @Override + public long skip(long n) { + if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n); + final int max = getWorkingMax(); + if (pos >= max) return 0; + final int remaining = max - pos; + if (n < remaining) { + pos = it.unimi.dsi.fastutil.SafeMath.safeLongToInt(pos + n); + return n; + } + n = remaining; + pos = max; + return n; + } + + @Override + public ObjectSpliterator trySplit() { + final int max = getWorkingMax(); + int retLen = (max - pos) >> 1; + if (retLen <= 1) return null; + // Update instance max with the last seen list size (if needed) before continuing + this.max = max; + int myNewPos = pos + retLen; + int oldPos = pos; + this.pos = myNewPos; + this.hasSplit = true; + return new Spliterator(oldPos, myNewPos, true); + } + } + + /** + * {@inheritDoc} + * + *

+ * The returned spliterator is late-binding; it will track structural changes after the current + * index, up until the first {@link java.util.Spliterator#trySplit() trySplit()}, at which point the + * maximum index will be fixed.
+ * Structural changes before the current index or after the first + * {@link java.util.Spliterator#trySplit() trySplit()} will result in unspecified behavior. + */ + @Override + public ObjectSpliterator spliterator() { + // If it wasn't for the possibility of the list being expanded or shrunk, + // we could return SPLITERATORS.wrap(a, 0, size). + return new Spliterator(); + } + + @Override + public void sort(final Comparator comp) { + if (comp == null) { + ObjectArrays.stableSort(a, 0, size); + } else { + ObjectArrays.stableSort(a, 0, size, comp); + } + } + + @Override + public void unstableSort(final Comparator comp) { + if (comp == null) { + ObjectArrays.unstableSort(a, 0, size); + } else { + ObjectArrays.unstableSort(a, 0, size, comp); + } + } + + /** + * Compares this type-specific array list to another one. + * + * @apiNote This method exists only for sake of efficiency. The implementation inherited from the + * abstract implementation would already work. + * + * @param l a type-specific array list. + * @return true if the argument contains the same elements of this type-specific array list. + */ + public boolean equals(final ImmutableWrappedArrayList l) { + return Arrays.equals(a, 0, size(), l.a, 0, l.size()); + } + + @SuppressWarnings({ "unchecked", "unlikely-arg-type" }) + @Override + public boolean equals(final Object o) { + if (o == this) return true; + if (o == null) return false; + if (!(o instanceof java.util.List)) return false; + if (o instanceof ImmutableWrappedArrayList) { + // Safe cast because we are only going to take elements from other list, never give them + return equals((ImmutableWrappedArrayList)o); + } + if (o instanceof ImmutableWrappedArrayList.SubList) { + // Safe cast because we are only going to take elements from other list, never give them + // Sublist has an optimized sub-array based comparison, reuse that. + return o.equals(this); + } + return super.equals(o); + } + + /** + * Compares this array list to another array list. + * + * @apiNote This method exists only for sake of efficiency. The implementation inherited from the + * abstract implementation would already work. + * + * @param l an array list. + * @return a negative integer, zero, or a positive integer as this list is lexicographically less + * than, equal to, or greater than the argument. + */ + @SuppressWarnings("unchecked") + public int compareTo(final ImmutableWrappedArrayList l) { + return Arrays.compare(a, 0, size(), l.a, 0, l.size(), (o1, o2) -> ((Comparable) o1).compareTo(o2)); + } + + @SuppressWarnings("unchecked") + @Override + public int compareTo(final java.util.List l) { + if (l instanceof ImmutableWrappedArrayList) { + return compareTo((ImmutableWrappedArrayList)l); + } + if (l instanceof ImmutableWrappedArrayList.SubList) { + // Must negate because we are inverting the order of the comparison. + return -((ImmutableWrappedArrayList.SubList)l).compareTo(this); + } + return super.compareTo(l); + } +}