From 8ab50ddefb73c56f473d0571e4e776b9995ef32a Mon Sep 17 00:00:00 2001 From: Andrea Cavalli Date: Fri, 20 Jan 2023 01:14:37 +0100 Subject: [PATCH] Partial new generator --- .gitignore | 1 + .../data/generator/plugin/ClassGenerator.java | 87 + .../data/generator/plugin/ComputedType.java | 487 +-- .../generator/plugin/ComputedTypeArray.java | 7 + .../plugin/ComputedTypeArrayNative.java | 147 + .../plugin/ComputedTypeArrayVersioned.java | 126 + .../generator/plugin/ComputedTypeBase.java | 146 + .../generator/plugin/ComputedTypeCustom.java | 130 + .../generator/plugin/ComputedTypeNative.java | 112 + .../plugin/ComputedTypeNullable.java | 7 + .../plugin/ComputedTypeNullableNative.java | 154 + .../plugin/ComputedTypeNullableVersioned.java | 122 + .../generator/plugin/ComputedTypeSuper.java | 129 + .../plugin/ComputedTypeSupplier.java | 27 +- .../generator/plugin/ComputedVersion.java | 73 +- .../plugin/CustomTypesConfiguration.java | 17 - .../data/generator/plugin/DataModel.java | 407 ++- .../data/generator/plugin/FieldLocation.java | 5 + .../data/generator/plugin/MavenPlugin.java | 5 +- .../data/generator/plugin/ParsedClass.java | 4 +- .../generator/plugin/SourcesGenerator.java | 2663 +---------------- .../data/generator/plugin/Standalone.java | 2 +- .../plugin/VersionChangeChecker.java | 14 +- .../data/generator/plugin/VersionedType.java | 10 +- .../plugin/classgen/GenBaseType.java | 21 + .../plugin/classgen/GenCurrentVersion.java | 191 ++ .../plugin/classgen/GenDataBaseX.java | 110 + .../plugin/classgen/GenDataSuperX.java | 85 + .../plugin/classgen/GenIBaseType.java | 39 + .../plugin/classgen/GenINullableBaseType.java | 39 + .../plugin/classgen/GenINullableIType.java | 34 + .../classgen/GenINullableSuperType.java | 39 + .../generator/plugin/classgen/GenIType.java | 31 + .../plugin/classgen/GenIVersion.java | 75 + .../plugin/classgen/GenNullableX.java | 137 + .../plugin/classgen/GenSuperType.java | 21 + .../generator/plugin/classgen/GenVersion.java | 228 ++ .../plugin/classgen/GenVersions.java | 62 + .../data/generator/plugin/TestGenerator.java | 6 +- .../generator/nativedata/Serializers.java | 28 + 40 files changed, 2767 insertions(+), 3261 deletions(-) create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ClassGenerator.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArray.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArrayNative.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArrayVersioned.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeBase.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeCustom.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNative.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullable.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullableNative.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullableVersioned.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSuper.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/FieldLocation.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenBaseType.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenCurrentVersion.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataBaseX.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataSuperX.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIBaseType.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableBaseType.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableIType.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableSuperType.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIType.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIVersion.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenNullableX.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSuperType.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersion.java create mode 100644 data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersions.java create mode 100644 data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/Serializers.java diff --git a/.gitignore b/.gitignore index 3e4d950..78ee22d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ .flattened-pom.xml /.idea/ +/data-generator-plugin/src/test/java/it/test/ diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ClassGenerator.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ClassGenerator.java new file mode 100644 index 0000000..655b64e --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ClassGenerator.java @@ -0,0 +1,87 @@ +package it.cavallium.data.generator.plugin; + +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static java.nio.file.StandardOpenOption.WRITE; + +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeSpec.Builder; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class ClassGenerator { + + private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final HashSet generatedFilesToDelete; + protected final DataModel dataModel; + protected final String basePackageName; + private final Path outPath; + protected final boolean deepCheckBeforeCreatingNewEqualInstances; + + public ClassGenerator(ClassGeneratorParams params) { + this.generatedFilesToDelete = params.generatedFilesToDelete; + this.dataModel = params.dataModel; + this.basePackageName = params.basePackageName; + this.outPath = params.outPath; + this.deepCheckBeforeCreatingNewEqualInstances = params.deepCheckBeforeCreatingNewEqualInstances; + } + + public void run() throws IOException { + for (GeneratedClass generatedClass : generateClasses().toList()) { + writeClass(generatedClass.packageName, generatedClass.content); + } + } + + private void writeClass(String classPackage, Builder versionsClass) throws IOException { + var sb = new StringBuilder(); + var typeSpec = versionsClass.build(); + var outJavaFile = outPath; + for (String part : classPackage.split("\\.")) { + outJavaFile = outJavaFile.resolve(part); + } + if (Files.notExists(outJavaFile)) { + Files.createDirectories(outJavaFile); + } + outJavaFile = outJavaFile.resolve(typeSpec.name + ".java"); + JavaFile.builder(classPackage, typeSpec).build().writeTo(sb); + String newFile = sb.toString(); + boolean mustWrite; + if (Files.isRegularFile(outJavaFile) && Files.isReadable(outJavaFile)) { + String oldFile = Files.readString(outJavaFile, StandardCharsets.UTF_8); + mustWrite = !oldFile.equals(newFile); + } else { + mustWrite = true; + } + if (mustWrite) { + logger.debug("File {} changed", outJavaFile); + Files.writeString(outJavaFile, newFile, StandardCharsets.UTF_8, TRUNCATE_EXISTING, CREATE, WRITE); + } else { + logger.debug("File {} is the same, unchanged", outJavaFile); + } + markFileAsCreated(generatedFilesToDelete, outPath, outJavaFile); + } + + private void markFileAsCreated(Set generatedFilesToDelete, Path basePath, Path filePath) { + generatedFilesToDelete.remove(basePath.relativize(filePath)); + } + + protected abstract Stream generateClasses(); + + public record GeneratedClass(String packageName, TypeSpec.Builder content) {} + + public record ClassGeneratorParams(HashSet generatedFilesToDelete, + DataModel dataModel, + String basePackageName, + Path outPath, + boolean deepCheckBeforeCreatingNewEqualInstances) {} +} 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 e72adfd..4a24a66 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 @@ -1,20 +1,34 @@ package it.cavallium.data.generator.plugin; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Objects; +import com.squareup.javapoet.TypeName; +import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; import java.util.stream.Stream; -public sealed interface ComputedType { +public sealed interface ComputedType permits VersionedComputedType, ComputedTypeArray, ComputedTypeCustom, + ComputedTypeNative, ComputedTypeNullable { String getName(); - sealed interface VersionedComputedType extends ComputedType { + TypeName getJTypeName(String basePackageName); - int getVersion(); + TypeName getJSerializerName(String basePackageName); - ComputedType withChangeAtVersion(int version, VersionChangeChecker versionChangeChecker); + FieldLocation getJSerializerInstance(String basePackageName); + + TypeName getJUpgraderName(String basePackageName); + + FieldLocation getJUpgraderInstance(String basePackageName); + + sealed interface VersionedComputedType extends ComputedType permits ComputedTypeArrayVersioned, ComputedTypeBase, + ComputedTypeNullableVersioned, ComputedTypeSuper { + + ComputedVersion getVersion(); + + default boolean shouldUpgradeAfter(ComputedVersion version) { + return !version.isCurrent() && version.getVersion() == this.getVersion().getVersion(); + } + + ComputedType withChangeAtVersion(ComputedVersion version, VersionChangeChecker versionChangeChecker); } /** @@ -27,461 +41,4 @@ public sealed interface ComputedType { */ Stream getDependents(); - final class ComputedBaseType implements VersionedComputedType { - - private final VersionedType type; - private final String stringRepresenter; - - private final LinkedHashMap data; - private LinkedHashMap computedData; - private final ComputedTypeSupplier computedTypeSupplier; - - public ComputedBaseType(VersionedType type, - String stringRepresenter, - LinkedHashMap data, - ComputedTypeSupplier computedTypeSupplier) { - this.type = type; - if (type.type().startsWith("~") || type.type().startsWith("-")) { - throw new IllegalStateException(); - } - this.computedTypeSupplier = computedTypeSupplier; - this.stringRepresenter = stringRepresenter; - this.data = data; - } - - public String getType() { - return type.type(); - } - - @Override - public int getVersion() { - return type.version(); - } - - @Override - public ComputedBaseType withChangeAtVersion(int version, VersionChangeChecker versionChangeChecker) { - var newData = new LinkedHashMap(); - data.forEach((k, v) -> newData.put(k, v.withVersionIfChanged(version, versionChangeChecker))); - return new ComputedBaseType(type.withVersion(version), stringRepresenter, newData, computedTypeSupplier); - } - - public String getStringRepresenter() { - return stringRepresenter; - } - - public LinkedHashMap getData() { - synchronized (this) { - if (computedData == null) { - computedData = new LinkedHashMap<>(); - data.forEach((k, v) -> computedData.put(k, computedTypeSupplier.get(v))); - } - } - return computedData; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ComputedBaseType that = (ComputedBaseType) o; - - if (!Objects.equals(type, that.type)) { - return false; - } - if (!Objects.equals(stringRepresenter, that.stringRepresenter)) { - return false; - } - return Objects.equals(data, that.data); - } - - @Override - public int hashCode() { - int result = type != null ? type.hashCode() : 0; - result = 31 * result + (stringRepresenter != null ? stringRepresenter.hashCode() : 0); - result = 31 * result + (data != null ? data.hashCode() : 0); - return result; - } - - @Override - public String getName() { - return type.type(); - } - - @Override - public Stream getDependencies() { - return this.data.values().stream().map(computedTypeSupplier::get); - } - - @Override - public Stream getDependents() { - return computedTypeSupplier.getDependents(type); - } - } - - final class ComputedCustomType implements ComputedType { - - private final String type; - private final String javaClass; - private final String serializer; - private final ComputedTypeSupplier computedTypeSupplier; - - public ComputedCustomType(String type, String javaClass, String serializer, ComputedTypeSupplier computedTypeSupplier) { - this.type = type; - this.javaClass = javaClass; - this.serializer = serializer; - this.computedTypeSupplier = computedTypeSupplier; - } - - public String getType() { - return type; - } - - public String getJavaClass() { - return javaClass; - } - - public String getSerializer() { - return serializer; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ComputedCustomType that = (ComputedCustomType) o; - - if (!Objects.equals(type, that.type)) { - return false; - } - if (!Objects.equals(javaClass, that.javaClass)) { - return false; - } - return Objects.equals(serializer, that.serializer); - } - - @Override - public int hashCode() { - int result = type != null ? type.hashCode() : 0; - result = 31 * result + (javaClass != null ? javaClass.hashCode() : 0); - result = 31 * result + (serializer != null ? serializer.hashCode() : 0); - return result; - } - - @Override - public String getName() { - return type; - } - - @Override - public Stream getDependencies() { - return Stream.of(); - } - - @Override - public Stream getDependents() { - return computedTypeSupplier.getDependents(new VersionedType(type, 0)); - } - } - - final class ComputedNullableType implements VersionedComputedType { - - private final VersionedType baseType; - - private ComputedType computedChild; - private final ComputedTypeSupplier computedTypeSupplier; - - public ComputedNullableType(VersionedType baseType, ComputedTypeSupplier computedTypeSupplier) { - this.baseType = baseType; - this.computedTypeSupplier = computedTypeSupplier; - } - - public String getBaseType() { - return baseType.type(); - } - - @Override - public int getVersion() { - return baseType.version(); - } - - @Override - public ComputedNullableType withChangeAtVersion(int version, VersionChangeChecker versionChangeChecker) { - return new ComputedNullableType(baseType.withVersion(version), computedTypeSupplier); - } - - public ComputedType child() { - synchronized (this) { - if (computedChild == null) { - computedChild = computedTypeSupplier.get(baseType); - } - } - if (computedChild instanceof ComputedNullableType) { - throw new IllegalStateException(); - } else if (computedChild instanceof ComputedArrayType) { - throw new IllegalStateException(); - } - return computedChild; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ComputedNullableType that = (ComputedNullableType) o; - - return Objects.equals(baseType, that.baseType); - } - - @Override - public int hashCode() { - return baseType != null ? baseType.hashCode() : 0; - } - - @Override - public String getName() { - return "-" + baseType.type(); - } - - @Override - public Stream getDependencies() { - return Stream.of(child()); - } - - @Override - public Stream getDependents() { - return computedTypeSupplier.getDependents(new VersionedType(getName(), 0)); - } - } - - final class ComputedSuperType implements VersionedComputedType { - - private final VersionedType type; - private final List subTypes; - - private List computedSubTypes; - private final ComputedTypeSupplier computedTypeSupplier; - - public ComputedSuperType(VersionedType type, - List subType, - ComputedTypeSupplier computedTypeSupplier) { - this.type = type; - this.subTypes = subType; - this.computedTypeSupplier = computedTypeSupplier; - } - - public String getType() { - return type.type(); - } - - @Override - public int getVersion() { - return type.version(); - } - - @Override - public ComputedSuperType withChangeAtVersion(int version, VersionChangeChecker versionChangeChecker) { - return new ComputedSuperType(type.withVersion(version), - subTypes.stream().map(subType -> subType.withVersionIfChanged(version, versionChangeChecker)).toList(), - computedTypeSupplier - ); - } - - public List subTypes() { - synchronized (this) { - if (computedSubTypes == null) { - computedSubTypes = new ArrayList<>(); - for (VersionedType subType : subTypes) { - computedSubTypes.add(computedTypeSupplier.get(subType)); - } - } - } - return computedSubTypes; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ComputedSuperType that = (ComputedSuperType) o; - - if (!Objects.equals(type, that.type)) { - return false; - } - return Objects.equals(subTypes, that.subTypes); - } - - @Override - public int hashCode() { - int result = type != null ? type.hashCode() : 0; - result = 31 * result + (subTypes != null ? subTypes.hashCode() : 0); - return result; - } - - @Override - public Stream getDependencies() { - return subTypes().stream(); - } - - @Override - public Stream getDependents() { - return computedTypeSupplier.getDependents(new VersionedType(getName(), 0)); - } - - @Override - public String getName() { - return type.type(); - } - } - - final class ComputedArrayType implements VersionedComputedType { - - private final VersionedType baseType; - - private ComputedType computedChild; - private final ComputedTypeSupplier computedTypeSupplier; - - public ComputedArrayType(VersionedType baseType, ComputedTypeSupplier computedTypeSupplier) { - this.baseType = baseType; - this.computedTypeSupplier = computedTypeSupplier; - } - - public String getBaseType() { - return baseType.type(); - } - - @Override - public int getVersion() { - return baseType.version(); - } - - @Override - public ComputedArrayType withChangeAtVersion(int version, VersionChangeChecker versionChangeChecker) { - return new ComputedArrayType(baseType.withVersion(version), computedTypeSupplier); - } - - public ComputedType child() { - synchronized (this) { - if (computedChild == null) { - computedChild = computedTypeSupplier.get(baseType); - } - } - if (computedChild instanceof ComputedNullableType) { - throw new IllegalStateException(); - } else if (computedChild instanceof ComputedArrayType) { - throw new IllegalStateException(); - } - return computedChild; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ComputedArrayType that = (ComputedArrayType) o; - - return Objects.equals(baseType, that.baseType); - } - - @Override - public int hashCode() { - return baseType != null ? baseType.hashCode() : 0; - } - - @Override - public String getName() { - return "§" + baseType.type(); - } - - @Override - public Stream getDependencies() { - return Stream.of(child()); - } - - @Override - public Stream getDependents() { - return computedTypeSupplier.getDependents(new VersionedType(getName(), 0)); - } - } - - final class ComputedNativeType implements ComputedType { - - private final String type; - private final ComputedTypeSupplier computedTypeSupplier; - - public ComputedNativeType(String type, ComputedTypeSupplier computedTypeSupplier) { - this.type = type; - this.computedTypeSupplier = computedTypeSupplier; - } - - public String getName() { - return type; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ComputedNativeType that = (ComputedNativeType) o; - - return Objects.equals(type, that.type); - } - - @Override - public int hashCode() { - return type != null ? type.hashCode() : 0; - } - - @Override - public Stream getDependencies() { - return Stream.of(); - } - - @Override - public Stream getDependents() { - return computedTypeSupplier.getDependents(new VersionedType(getName(), 0)); - } - - public static List get(ComputedTypeSupplier computedTypeSupplier) { - return Stream.of("String", - "boolean", - "short", - "char", - "int", - "long", - "float", - "double", - "byte", - "Int52").map(name -> new ComputedNativeType(name, computedTypeSupplier)).toList(); - } - } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArray.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArray.java new file mode 100644 index 0000000..4a82d56 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArray.java @@ -0,0 +1,7 @@ +package it.cavallium.data.generator.plugin; + +public sealed interface ComputedTypeArray extends ComputedType permits ComputedTypeArrayNative, + ComputedTypeArrayVersioned { + + ComputedType getBase(); +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArrayNative.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArrayNative.java new file mode 100644 index 0000000..a2b0c2c --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArrayNative.java @@ -0,0 +1,147 @@ +package it.cavallium.data.generator.plugin; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import it.cavallium.data.generator.nativedata.ArrayInt52Serializer; +import it.cavallium.data.generator.nativedata.ArrayStringSerializer; +import it.cavallium.data.generator.nativedata.ArraybooleanSerializer; +import it.cavallium.data.generator.nativedata.ArraybyteSerializer; +import it.cavallium.data.generator.nativedata.ArraycharSerializer; +import it.cavallium.data.generator.nativedata.ArraydoubleSerializer; +import it.cavallium.data.generator.nativedata.ArrayfloatSerializer; +import it.cavallium.data.generator.nativedata.ArrayintSerializer; +import it.cavallium.data.generator.nativedata.ArraylongSerializer; +import it.cavallium.data.generator.nativedata.ArrayshortSerializer; +import it.cavallium.data.generator.nativedata.Serializers; +import it.unimi.dsi.fastutil.booleans.BooleanList; +import it.unimi.dsi.fastutil.bytes.ByteList; +import it.unimi.dsi.fastutil.chars.CharList; +import it.unimi.dsi.fastutil.doubles.DoubleList; +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.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +public final class ComputedTypeArrayNative implements ComputedTypeArray { + + private final String baseType; + + private ComputedTypeNative computedChild; + private final ComputedTypeSupplier computedTypeSupplier; + + public ComputedTypeArrayNative(String baseType, ComputedTypeSupplier computedTypeSupplier) { + this.baseType = baseType; + this.computedTypeSupplier = computedTypeSupplier; + } + + public ComputedType getBase() { + return child(); + } + + public ComputedTypeNative child() { + synchronized (this) { + if (computedChild == null) { + var computedChild = computedTypeSupplier.get(baseType); + if (computedChild instanceof ComputedTypeNative computedTypeNative) { + this.computedChild = computedTypeNative; + } else { + throw new IllegalStateException(); + } + } + } + return computedChild; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ComputedTypeArrayNative that = (ComputedTypeArrayNative) o; + + return Objects.equals(baseType, that.baseType); + } + + @Override + public int hashCode() { + return baseType != null ? baseType.hashCode() : 0; + } + + @Override + public String getName() { + return "§" + baseType; + } + + @Override + public TypeName getJTypeName(String basePackageName) { + return switch (baseType) { + case "boolean" -> ClassName.get(BooleanList.class); + case "byte" -> ClassName.get(ByteList.class); + case "short" -> ClassName.get(ShortList.class); + case "char" -> ClassName.get(CharList.class); + case "int" -> ClassName.get(IntList.class); + case "long" -> ClassName.get(LongList.class); + case "float" -> ClassName.get(FloatList.class); + case "double" -> ClassName.get(DoubleList.class); + default -> ParameterizedTypeName.get(ClassName.get(List.class), + computedTypeSupplier.get(baseType).getJTypeName(basePackageName)); + }; + } + + @Override + public ClassName getJSerializerName(String basePackageName) { + return switch (baseType) { + case "boolean" -> ClassName.get(ArraybooleanSerializer.class); + case "byte" -> ClassName.get(ArraybyteSerializer.class); + case "short" -> ClassName.get(ArrayshortSerializer.class); + case "char" -> ClassName.get(ArraycharSerializer.class); + case "int" -> ClassName.get(ArrayintSerializer.class); + case "long" -> ClassName.get(ArraylongSerializer.class); + case "float" -> ClassName.get(ArrayfloatSerializer.class); + case "double" -> ClassName.get(ArraydoubleSerializer.class); + case "String" -> ClassName.get(ArrayStringSerializer.class); + case "Int52" -> ClassName.get(ArrayInt52Serializer.class); + default -> throw new UnsupportedOperationException(); + }; + } + + @Override + public FieldLocation getJSerializerInstance(String basePackageName) { + var className = ClassName.get(Serializers.class); + var serializerFieldName = "Array" + baseType + "SerializerInstance"; + return new FieldLocation(className, serializerFieldName); + } + + @Override + public TypeName getJUpgraderName(String basePackageName) { + throw new UnsupportedOperationException("Not upgradable"); + } + + @Override + public FieldLocation getJUpgraderInstance(String basePackageName) { + throw new UnsupportedOperationException("Not upgradable"); + } + + @Override + public Stream getDependencies() { + return Stream.of(child()); + } + + @Override + public Stream getDependents() { + return computedTypeSupplier.getDependents(getName()); + } + + @Override + public String toString() { + return baseType + "[]"; + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArrayVersioned.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArrayVersioned.java new file mode 100644 index 0000000..34cb732 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeArrayVersioned.java @@ -0,0 +1,126 @@ +package it.cavallium.data.generator.plugin; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +public final class ComputedTypeArrayVersioned implements VersionedComputedType, ComputedTypeArray { + + private final VersionedType baseType; + + private VersionedComputedType computedChild; + private final ComputedTypeSupplier computedTypeSupplier; + + public ComputedTypeArrayVersioned(VersionedType baseType, ComputedTypeSupplier computedTypeSupplier) { + this.baseType = baseType; + this.computedTypeSupplier = computedTypeSupplier; + } + + public VersionedComputedType getBase() { + return child(); + } + + @Override + public ComputedVersion getVersion() { + return baseType.version(); + } + + @Override + public ComputedTypeArrayVersioned withChangeAtVersion(ComputedVersion version, + VersionChangeChecker versionChangeChecker) { + return new ComputedTypeArrayVersioned(baseType.withVersion(version), + computedTypeSupplier + ); + } + + public VersionedComputedType child() { + synchronized (this) { + if (computedChild == null) { + var computedChild = computedTypeSupplier.get(baseType); + if (computedChild instanceof ComputedTypeNullableVersioned) { + throw new IllegalStateException(); + } else if (computedChild instanceof ComputedTypeArrayVersioned) { + throw new IllegalStateException(); + } else if (computedChild instanceof VersionedComputedType versionedComputedType) { + this.computedChild = versionedComputedType; + } else { + throw new IllegalStateException(); + } + } + } + return computedChild; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ComputedTypeArrayVersioned that = (ComputedTypeArrayVersioned) o; + + return Objects.equals(baseType, that.baseType); + } + + @Override + public int hashCode() { + return baseType != null ? baseType.hashCode() : 0; + } + + @Override + public String getName() { + return "§" + baseType.type(); + } + + @Override + public TypeName getJTypeName(String basePackageName) { + return ParameterizedTypeName.get(ClassName.get(List.class), + computedTypeSupplier.get(baseType).getJTypeName(basePackageName)); + } + + @Override + public ClassName getJSerializerName(String basePackageName) { + return ClassName.get(baseType.version().getSerializersPackage(basePackageName), "Array" + baseType.type() + "Serializer"); + } + + @Override + public FieldLocation getJSerializerInstance(String basePackageName) { + var className = ClassName.get(baseType.version().getPackage(basePackageName), "Version"); + var serializerFieldName = "Array" + baseType.type() + "SerializerInstance"; + return new FieldLocation(className, serializerFieldName); + } + + @Override + public TypeName getJUpgraderName(String basePackageName) { + return ClassName.get(baseType.version().getUpgradersPackage(basePackageName), "Array" + baseType.type() + "Upgrader"); + } + + @Override + public FieldLocation getJUpgraderInstance(String basePackageName) { + var className = ClassName.get(baseType.version().getPackage(basePackageName), "Version"); + var upgraderFieldName = "Array" + baseType.type() + "UpgraderInstance"; + return new FieldLocation(className, upgraderFieldName); + } + + @Override + public Stream getDependencies() { + return Stream.of(child()); + } + + @Override + public Stream getDependents() { + return computedTypeSupplier.getDependents(getName()); + } + + @Override + public String toString() { + return baseType.type() + "[] (v" + getVersion() + ")"; + } +} 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 new file mode 100644 index 0000000..12b5c10 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeBase.java @@ -0,0 +1,146 @@ +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; +import java.util.stream.Stream; + +public final class ComputedTypeBase implements VersionedComputedType { + + private final VersionedType type; + private final String stringRepresenter; + + private final LinkedHashMap data; + private LinkedHashMap computedData; + private final ComputedTypeSupplier computedTypeSupplier; + + public ComputedTypeBase(VersionedType type, + String stringRepresenter, + LinkedHashMap data, + ComputedTypeSupplier computedTypeSupplier) { + this.type = type; + if (type.type().startsWith("~") || type.type().startsWith("-")) { + throw new IllegalStateException(); + } + this.computedTypeSupplier = computedTypeSupplier; + this.stringRepresenter = stringRepresenter; + this.data = data; + } + + public String getType() { + return type.type(); + } + + @Override + public ComputedVersion getVersion() { + return type.version(); + } + + @Override + public ComputedTypeBase withChangeAtVersion(ComputedVersion version, + VersionChangeChecker versionChangeChecker) { + var newData = new LinkedHashMap(); + data.forEach((k, v) -> newData.put(k, v.withVersionIfChanged(version, versionChangeChecker))); + return new ComputedTypeBase(type.withVersion(version), + stringRepresenter, + newData, + computedTypeSupplier + ); + } + + public String getStringRepresenter() { + return stringRepresenter; + } + + public LinkedHashMap getData() { + synchronized (this) { + if (computedData == null) { + computedData = new LinkedHashMap<>(); + data.forEach((k, v) -> computedData.put(k, computedTypeSupplier.get(v))); + } + } + return computedData; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ComputedTypeBase that = (ComputedTypeBase) o; + + if (!Objects.equals(type, that.type)) { + return false; + } + if (!Objects.equals(stringRepresenter, that.stringRepresenter)) { + return false; + } + return Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + int result = type != null ? type.hashCode() : 0; + result = 31 * result + (stringRepresenter != null ? stringRepresenter.hashCode() : 0); + result = 31 * result + (data != null ? data.hashCode() : 0); + return result; + } + + @Override + public String getName() { + return type.type(); + } + + @Override + public TypeName getJTypeName(String basePackageName) { + return ClassName.get(getVersion().getDataPackage(basePackageName), type.type()); + } + + @Override + public ClassName getJSerializerName(String basePackageName) { + return ClassName.get(type.version().getSerializersPackage(basePackageName), type.type() + "Serializer"); + } + + @Override + public FieldLocation getJSerializerInstance(String basePackageName) { + var className = ClassName.get(type.version().getPackage(basePackageName), "Version"); + var serializerFieldName = type.type() + "SerializerInstance"; + return new FieldLocation(className, serializerFieldName); + } + + @Override + public TypeName getJUpgraderName(String basePackageName) { + return ClassName.get(type.version().getUpgradersPackage(basePackageName), type.type() + "Upgrader"); + } + + @Override + public FieldLocation getJUpgraderInstance(String basePackageName) { + var className = ClassName.get(type.version().getPackage(basePackageName), "Version"); + var upgraderFieldName = type.type() + "UpgraderInstance"; + return new FieldLocation(className, upgraderFieldName); + } + + @Override + public Stream getDependencies() { + return this.data.values().stream().map(computedTypeSupplier::get); + } + + @Override + public Stream getDependents() { + return computedTypeSupplier.getDependents(type); + } + + @Override + public String toString() { + return type.type() + " (base, v" + getVersion() + ")"; + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeCustom.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeCustom.java new file mode 100644 index 0000000..751f40f --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeCustom.java @@ -0,0 +1,130 @@ +package it.cavallium.data.generator.plugin; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import java.util.Objects; +import java.util.stream.Stream; + +public final class ComputedTypeCustom implements ComputedType { + + private final String type; + private final ComputedVersion latestVersion; + private final String javaClass; + private final String serializer; + private final ComputedTypeSupplier computedTypeSupplier; + private final TypeName typeName; + + public ComputedTypeCustom(String type, + String javaClass, + String serializer, + ComputedTypeSupplier computedTypeSupplier, + ComputedVersion latestVersion) { + this.type = type; + this.latestVersion = latestVersion; + this.javaClass = javaClass; + this.serializer = serializer; + this.computedTypeSupplier = computedTypeSupplier; + { + int indexOfGeneric; + if ((indexOfGeneric = javaClass.indexOf("<")) == -1) { + this.typeName = ClassName.bestGuess(javaClass.trim()); + } else { + var rawTypesArray = javaClass.substring(indexOfGeneric + 1, javaClass.length() - 1).split(","); + var genericsResult = new TypeName[rawTypesArray.length]; + int i = 0; + for (String rawType : rawTypesArray) { + genericsResult[i] = ClassName.bestGuess(rawType.trim()); + i++; + } + var base = ClassName.bestGuess(javaClass.substring(0, indexOfGeneric).trim()); + this.typeName = ParameterizedTypeName.get(base, genericsResult); + } + } + } + + public String getType() { + return type; + } + + public String getJavaClass() { + return javaClass; + } + + public String getSerializer() { + return serializer; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + it.cavallium.data.generator.plugin.ComputedTypeCustom that = (it.cavallium.data.generator.plugin.ComputedTypeCustom) o; + + if (!Objects.equals(type, that.type)) { + return false; + } + if (!Objects.equals(javaClass, that.javaClass)) { + return false; + } + return Objects.equals(serializer, that.serializer); + } + + @Override + public int hashCode() { + int result = type != null ? type.hashCode() : 0; + result = 31 * result + (javaClass != null ? javaClass.hashCode() : 0); + result = 31 * result + (serializer != null ? serializer.hashCode() : 0); + return result; + } + + @Override + public String getName() { + return type; + } + + @Override + public TypeName getJTypeName(String basePackageName) { + return typeName; + } + + @Override + public TypeName getJSerializerName(String basePackageName) { + return ClassName.bestGuess(serializer); + } + + @Override + public FieldLocation getJSerializerInstance(String basePackageName) { + return new FieldLocation(ClassName.get(latestVersion.getPackage(basePackageName), "Version"), type + "SerializerInstance"); + } + + @Override + public TypeName getJUpgraderName(String basePackageName) { + throw new UnsupportedOperationException("Not upgradable"); + } + + @Override + public FieldLocation getJUpgraderInstance(String basePackageName) { + throw new UnsupportedOperationException("Not upgradable"); + } + + @Override + public Stream getDependencies() { + return Stream.of(); + } + + @Override + public Stream getDependents() { + return computedTypeSupplier.getDependents(type); + } + + @Override + public String toString() { + return type + " (custom)"; + } +} 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 new file mode 100644 index 0000000..5cfb7cf --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNative.java @@ -0,0 +1,112 @@ +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; +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.stream.Stream; + +public final class ComputedTypeNative implements ComputedType { + + private final String type; + private final ComputedTypeSupplier computedTypeSupplier; + + public ComputedTypeNative(String type, ComputedTypeSupplier computedTypeSupplier) { + this.type = type; + this.computedTypeSupplier = computedTypeSupplier; + } + + public String getName() { + return type; + } + + @Override + public TypeName getJTypeName(String basePackageName) { + return switch (type) { + case "String" -> ClassName.get(String.class); + case "boolean" -> TypeName.BOOLEAN; + case "short" -> TypeName.SHORT; + case "char" -> TypeName.CHAR; + case "int" -> TypeName.INT; + case "long" -> TypeName.LONG; + case "float" -> TypeName.FLOAT; + case "double" -> TypeName.DOUBLE; + case "byte" -> TypeName.BYTE; + case "Int52" -> ClassName.get(it.cavallium.data.generator.nativedata.Int52.class); + default -> throw new UnsupportedOperationException(type + " is not a known native type"); + }; + } + + @Override + public TypeName getJSerializerName(String basePackageName) { + return switch (type) { + case "String" -> ClassName.get(StringSerializer.class); + case "boolean", "byte", "short", "char", "int", "long", "float", "double" -> + throw new UnsupportedOperationException("Type " + type + + " is a native type, so it doesn't have a serializer"); + case "Int52" -> ClassName.get(Int52Serializer.class); + default -> throw new UnsupportedOperationException(type + " is not a known native type"); + }; + } + + @Override + public FieldLocation getJSerializerInstance(String basePackageName) { + return new FieldLocation(ClassName.get(Serializers.class), type + "SerializerInstance"); + } + + @Override + public TypeName getJUpgraderName(String basePackageName) { + throw new UnsupportedOperationException("Type " + type + " is a native type, so it doesn't have a upgrader"); + } + + @Override + public FieldLocation getJUpgraderInstance(String basePackageName) { + throw new UnsupportedOperationException("Type " + type + " is a native type, so it doesn't have a upgrader"); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + it.cavallium.data.generator.plugin.ComputedTypeNative that = (it.cavallium.data.generator.plugin.ComputedTypeNative) o; + + return Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return type != null ? type.hashCode() : 0; + } + + @Override + public Stream getDependencies() { + return Stream.of(); + } + + @Override + public Stream getDependents() { + return computedTypeSupplier.getDependents(getName()); + } + + public static List get(ComputedTypeSupplier computedTypeSupplier) { + return Stream + .of("String", "boolean", "short", "char", "int", "long", "float", "double", "byte", "Int52") + .map(name -> new it.cavallium.data.generator.plugin.ComputedTypeNative(name, computedTypeSupplier)) + .toList(); + } + + @Override + public String toString() { + return type + " (native)"; + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullable.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullable.java new file mode 100644 index 0000000..87d332e --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullable.java @@ -0,0 +1,7 @@ +package it.cavallium.data.generator.plugin; + +public sealed interface ComputedTypeNullable extends ComputedType permits ComputedTypeNullableNative, + ComputedTypeNullableVersioned { + + ComputedType getBase(); +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullableNative.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullableNative.java new file mode 100644 index 0000000..871ea0e --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullableNative.java @@ -0,0 +1,154 @@ +package it.cavallium.data.generator.plugin; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import it.cavallium.data.generator.nativedata.NullableInt52; +import it.cavallium.data.generator.nativedata.NullableInt52Serializer; +import it.cavallium.data.generator.nativedata.NullableString; +import it.cavallium.data.generator.nativedata.NullableStringSerializer; +import it.cavallium.data.generator.nativedata.Nullableboolean; +import it.cavallium.data.generator.nativedata.NullablebooleanSerializer; +import it.cavallium.data.generator.nativedata.Nullablebyte; +import it.cavallium.data.generator.nativedata.NullablebyteSerializer; +import it.cavallium.data.generator.nativedata.Nullablechar; +import it.cavallium.data.generator.nativedata.NullablecharSerializer; +import it.cavallium.data.generator.nativedata.Nullabledouble; +import it.cavallium.data.generator.nativedata.NullabledoubleSerializer; +import it.cavallium.data.generator.nativedata.Nullablefloat; +import it.cavallium.data.generator.nativedata.NullablefloatSerializer; +import it.cavallium.data.generator.nativedata.Nullableint; +import it.cavallium.data.generator.nativedata.NullableintSerializer; +import it.cavallium.data.generator.nativedata.Nullablelong; +import it.cavallium.data.generator.nativedata.NullablelongSerializer; +import it.cavallium.data.generator.nativedata.Nullableshort; +import it.cavallium.data.generator.nativedata.NullableshortSerializer; +import it.cavallium.data.generator.nativedata.Serializers; +import java.util.Objects; +import java.util.stream.Stream; + +public final class ComputedTypeNullableNative implements ComputedTypeNullable { + + private final String baseType; + private final ComputedVersion latestVersion; + + private ComputedTypeNative computedChild; + private final ComputedTypeSupplier computedTypeSupplier; + + public ComputedTypeNullableNative(String baseType, ComputedVersion latestVersion, ComputedTypeSupplier computedTypeSupplier) { + this.baseType = baseType; + this.latestVersion = latestVersion; + this.computedTypeSupplier = computedTypeSupplier; + } + + public ComputedTypeNative getBase() { + return child(); + } + + public ComputedTypeNative child() { + synchronized (this) { + if (computedChild == null) { + var computedChild = computedTypeSupplier.get(baseType); + if (computedChild instanceof ComputedTypeNative computedTypeNative) { + this.computedChild = computedTypeNative; + } else { + throw new IllegalStateException(); + } + } + } + return computedChild; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ComputedTypeNullableNative that = (ComputedTypeNullableNative) o; + + return Objects.equals(baseType, that.baseType); + } + + @Override + public int hashCode() { + return baseType != null ? baseType.hashCode() : 0; + } + + @Override + public String getName() { + return "-" + baseType; + } + + @Override + public TypeName getJTypeName(String basePackageName) { + return switch (baseType) { + case "boolean" -> ClassName.get(Nullableboolean.class); + case "byte" -> ClassName.get(Nullablebyte.class); + case "short" -> ClassName.get(Nullableshort.class); + case "char" -> ClassName.get(Nullablechar.class); + case "int" -> ClassName.get(Nullableint.class); + case "long" -> ClassName.get(Nullablelong.class); + case "float" -> ClassName.get(Nullablefloat.class); + case "double" -> ClassName.get(Nullabledouble.class); + case "String" -> ClassName.get(NullableString.class); + case "Int52" -> ClassName.get(NullableInt52.class); + default -> ClassName.get(latestVersion.getDataNullablesPackage(basePackageName), "Nullable" + baseType); + }; + } + + @Override + public ClassName getJSerializerName(String basePackageName) { + return switch (baseType) { + case "boolean" -> ClassName.get(NullablebooleanSerializer.class); + case "byte" -> ClassName.get(NullablebyteSerializer.class); + case "short" -> ClassName.get(NullableshortSerializer.class); + case "char" -> ClassName.get(NullablecharSerializer.class); + case "int" -> ClassName.get(NullableintSerializer.class); + case "long" -> ClassName.get(NullablelongSerializer.class); + case "float" -> ClassName.get(NullablefloatSerializer.class); + case "double" -> ClassName.get(NullabledoubleSerializer.class); + case "String" -> ClassName.get(NullableStringSerializer.class); + case "Int52" -> ClassName.get(NullableInt52Serializer.class); + default -> throw new UnsupportedOperationException(); + }; + } + + @Override + public FieldLocation getJSerializerInstance(String basePackageName) { + var className = switch (baseType) { + case "boolean", "byte", "short", "char", "int", "long", "float", "double", "String", "Int52" -> + ClassName.get(Serializers.class); + default -> throw new UnsupportedOperationException(); + }; + var serializerFieldName = "Nullable" + baseType + "SerializerInstance"; + return new FieldLocation(className, serializerFieldName); + } + + @Override + public TypeName getJUpgraderName(String basePackageName) { + throw new UnsupportedOperationException("Type " + baseType + " is a native type, so it doesn't have a upgrader"); + } + + @Override + public FieldLocation getJUpgraderInstance(String basePackageName) { + throw new UnsupportedOperationException("Type " + baseType + " is a native type, so it doesn't have a upgrader"); + } + + @Override + public Stream getDependencies() { + return Stream.of(child()); + } + + @Override + public Stream getDependents() { + return computedTypeSupplier.getDependents(getName()); + } + + @Override + public String toString() { + return "-" + baseType; + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullableVersioned.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullableVersioned.java new file mode 100644 index 0000000..7f8ae94 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeNullableVersioned.java @@ -0,0 +1,122 @@ +package it.cavallium.data.generator.plugin; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; +import java.util.Objects; +import java.util.stream.Stream; + +public final class ComputedTypeNullableVersioned implements ComputedTypeNullable, VersionedComputedType { + + private final VersionedType baseType; + + private ComputedType computedChild; + private final ComputedTypeSupplier computedTypeSupplier; + + public ComputedTypeNullableVersioned(VersionedType baseType, ComputedTypeSupplier computedTypeSupplier) { + this.baseType = baseType; + this.computedTypeSupplier = computedTypeSupplier; + } + + public VersionedComputedType getBase() { + return (VersionedComputedType) computedTypeSupplier.get(baseType); + } + + @Override + public ComputedVersion getVersion() { + return baseType.version(); + } + + @Override + public ComputedTypeNullableVersioned withChangeAtVersion(ComputedVersion version, + VersionChangeChecker versionChangeChecker) { + return new ComputedTypeNullableVersioned(baseType.withVersion(version), + computedTypeSupplier + ); + } + + public ComputedType child() { + synchronized (this) { + if (computedChild == null) { + computedChild = computedTypeSupplier.get(baseType); + } + } + if (computedChild instanceof ComputedTypeNullableVersioned) { + throw new IllegalStateException(); + } else if (computedChild instanceof ComputedTypeArrayVersioned) { + throw new IllegalStateException(); + } + return computedChild; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ComputedTypeNullableVersioned that = (ComputedTypeNullableVersioned) o; + + return Objects.equals(baseType, that.baseType); + } + + @Override + public int hashCode() { + return baseType != null ? baseType.hashCode() : 0; + } + + @Override + public String getName() { + return "-" + baseType.type(); + } + + @Override + public TypeName getJTypeName(String basePackageName) { + return ClassName.get(baseType.version().getDataNullablesPackage(basePackageName), + "Nullable" + baseType.type()); + } + + @Override + public ClassName getJSerializerName(String basePackageName) { + return ClassName.get(baseType.version().getSerializersPackage(basePackageName), + "Nullable" + baseType.type() + "Serializer"); + } + + @Override + public FieldLocation getJSerializerInstance(String basePackageName) { + var className = ClassName.get(baseType.version().getPackage(basePackageName), "Version"); + var serializerFieldName = "Nullable" + baseType.type() + "SerializerInstance"; + return new FieldLocation(className, serializerFieldName); + } + + @Override + public TypeName getJUpgraderName(String basePackageName) { + return ClassName.get(baseType.version().getSerializersPackage(basePackageName), + "Nullable" + baseType.type() + "Upgrader"); + } + + @Override + public FieldLocation getJUpgraderInstance(String basePackageName) { + var className = ClassName.get(baseType.version().getPackage(basePackageName), "Version"); + var upgraderFieldName = "Nullable" + baseType.type() + "UpgraderInstance"; + return new FieldLocation(className, upgraderFieldName); + } + + @Override + public Stream getDependencies() { + return Stream.of(child()); + } + + @Override + public Stream getDependents() { + return computedTypeSupplier.getDependents(getName()); + } + + @Override + public String toString() { + return "-" + baseType.type() + " (v" + getVersion() + ")"; + } +} 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 new file mode 100644 index 0000000..454a351 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSuper.java @@ -0,0 +1,129 @@ +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; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +public final class ComputedTypeSuper implements VersionedComputedType { + + private final VersionedType type; + private final List subTypes; + + private List computedSubTypes; + private final ComputedTypeSupplier computedTypeSupplier; + + public ComputedTypeSuper(VersionedType type, List subType, ComputedTypeSupplier computedTypeSupplier) { + this.type = type; + this.subTypes = subType; + this.computedTypeSupplier = computedTypeSupplier; + } + + public String getType() { + return type.type(); + } + + @Override + public ComputedVersion getVersion() { + return type.version(); + } + + @Override + public it.cavallium.data.generator.plugin.ComputedTypeSuper withChangeAtVersion(ComputedVersion version, + VersionChangeChecker versionChangeChecker) { + return new it.cavallium.data.generator.plugin.ComputedTypeSuper(type.withVersion(version), + subTypes.stream().map(subType -> subType.withVersionIfChanged(version, versionChangeChecker)).toList(), + computedTypeSupplier + ); + } + + public List subTypes() { + synchronized (this) { + if (computedSubTypes == null) { + computedSubTypes = new ArrayList<>(); + for (VersionedType subType : subTypes) { + computedSubTypes.add(computedTypeSupplier.get(subType)); + } + } + } + return computedSubTypes; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + it.cavallium.data.generator.plugin.ComputedTypeSuper that = (it.cavallium.data.generator.plugin.ComputedTypeSuper) o; + + if (!Objects.equals(type, that.type)) { + return false; + } + return Objects.equals(subTypes, that.subTypes); + } + + @Override + public int hashCode() { + int result = type != null ? type.hashCode() : 0; + result = 31 * result + (subTypes != null ? subTypes.hashCode() : 0); + return result; + } + + @Override + public Stream getDependencies() { + return subTypes().stream(); + } + + @Override + public Stream getDependents() { + return computedTypeSupplier.getDependents(getName()); + } + + @Override + public String getName() { + return type.type(); + } + + @Override + public TypeName getJTypeName(String basePackageName) { + return ClassName.get(getVersion().getDataPackage(basePackageName), type.type()); + } + + @Override + public ClassName getJSerializerName(String basePackageName) { + return ClassName.get(type.version().getSerializersPackage(basePackageName), type.type() + "Serializer"); + } + + @Override + public FieldLocation getJSerializerInstance(String basePackageName) { + var className = ClassName.get(type.version().getPackage(basePackageName), "Version"); + var serializerFieldName = type.type() + "SerializerInstance"; + return new FieldLocation(className, serializerFieldName); + } + + @Override + public TypeName getJUpgraderName(String basePackageName) { + return ClassName.get(type.version().getSerializersPackage(basePackageName), type.type() + "Upgrader"); + } + + @Override + public FieldLocation getJUpgraderInstance(String basePackageName) { + var className = ClassName.get(type.version().getPackage(basePackageName), "Version"); + var upgraderFieldName = type.type() + "UpgraderInstance"; + return new FieldLocation(className, upgraderFieldName); + } + + @Override + public String toString() { + return type.type() + " (super, v" + getVersion() + ")"; + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSupplier.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSupplier.java index eebf72f..eda5c7f 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSupplier.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSupplier.java @@ -2,6 +2,7 @@ package it.cavallium.data.generator.plugin; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -12,31 +13,45 @@ public class ComputedTypeSupplier { private final Int2ObjectMap> computedTypeMap; private final Int2ObjectMap>> computedTypeDependentsCacheMap = new Int2ObjectOpenHashMap<>(); + private final ComputedVersion currentVersion; - public ComputedTypeSupplier(Int2ObjectMap> computedTypeMap) { + public ComputedTypeSupplier(Int2ObjectMap> computedTypeMap, ComputedVersion currentVersion) { this.computedTypeMap = computedTypeMap; + this.currentVersion = currentVersion; } public ComputedType get(VersionedType type) { - var computedType = computedTypeMap.get(type.version()).get(type.type()); + var computedType = computedTypeMap.get(type.version().getVersion()).get(type.type()); if (computedType == null) { throw new IllegalStateException("Type " + type + " does not exist"); } return computedType; } + public ComputedType get(String type) { + return get(new VersionedType(type, currentVersion)); + } + public Stream getDependencies(VersionedType type) { - return computedTypeMap.get(type.version()).get(type.type()).getDependencies(); + return computedTypeMap.get(type.version().getVersion()).get(type.type()).getDependencies(); } public Stream getDependents(VersionedType type) { synchronized (computedTypeDependentsCacheMap) { return computedTypeDependentsCacheMap - .computeIfAbsent(type.version(), x -> new HashMap<>()) + .computeIfAbsent(type.version().getVersion(), x -> new HashMap<>()) .computeIfAbsent(type.type(), - typeName -> computedTypeMap.get(type.version()).values().stream().filter(computedType -> - computedType.getDependencies().anyMatch(y -> Objects.equals(y.getName(), typeName))).toList()) + typeName -> Objects.requireNonNull(computedTypeMap.get(type.version().getVersion()), () -> "Version " + type.version() + " does not exist") + .values().stream().filter(computedType -> + computedType.getDependencies().anyMatch(y -> Objects.equals(y.getName(), typeName))).toList()) .stream(); } } + + /** + * Get dependents from the current version + */ + public Stream getDependents(String type) { + return getDependents(new VersionedType(type, currentVersion)); + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedVersion.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedVersion.java index 92afec9..a56a5ba 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedVersion.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedVersion.java @@ -1,25 +1,27 @@ package it.cavallium.data.generator.plugin; +import static it.cavallium.data.generator.plugin.DataModel.joinPackage; + import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.StringJoiner; +import org.jetbrains.annotations.NotNull; -public class ComputedVersion { +public class ComputedVersion implements Comparable { private final String name; - private final Map classMap; private final int version; private final boolean current; public DetailsConfiguration details; public List transformations; - public ComputedVersion(ParsedVersion value, int version, boolean current, String versionName, Map classMap) { + public ComputedVersion(ParsedVersion value, int version, boolean current, String versionName) { this.details = value.details; this.transformations = value.transformations; + this.name = versionName; this.version = version; this.current = current; - this.name = versionName; - this.classMap = classMap; } public int getVersion() { @@ -30,10 +32,6 @@ public class ComputedVersion { return name; } - public Map getClassMap() { - return classMap; - } - @Override public boolean equals(Object o) { if (this == o) { @@ -43,46 +41,65 @@ public class ComputedVersion { return false; } ComputedVersion that = (ComputedVersion) o; - return Objects.equals(details, that.details) - && Objects.equals(transformations, that.transformations); + return Objects.equals(version, that.version); } @Override public int hashCode() { - int hash = 0; - hash += ConfigUtils.hashCode(details); - hash += ConfigUtils.hashCode(transformations); - return hash; + return ConfigUtils.hashCode(version); } public String getPackage(String basePackageName) { if (current) { return joinPackage(basePackageName, "current"); } else { - return joinPackage(basePackageName, "v" + getVersionCompleteInt()); + return joinPackage(basePackageName, "v" + version); } } + public String getDataPackage(String basePackageName) { + return joinPackage(getPackage(basePackageName), "data"); + } + + public String getUpgradersPackage(String basePackageName) { + return joinPackage(getPackage(basePackageName), "upgraders"); + } + + public String getSerializersPackage(String basePackageName) { + return joinPackage(getPackage(basePackageName), "serializers"); + } + + public String getDataNullablesPackage(String basePackageName) { + return joinPackage(getDataPackage(basePackageName), "nullables"); + } + public String getVersionVarName() { return "V" + version; } - private String getVersionCompleteInt() { + public String getVersionShortInt() { return Integer.toString(version); } - private String joinPackage(String basePackageName, String packageName) { - if (basePackageName.isBlank()) { - basePackageName = "org.generated"; - } - if (packageName.isBlank()) { - return basePackageName; - } else { - return basePackageName + "." + packageName; - } - } - public boolean isCurrent() { return current; } + + @Override + public int compareTo(@NotNull ComputedVersion o) { + return Integer.compare(version, o.version); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(version); + sb.append(" ("); + sb.append(name); + if (current) { + sb.append(", current"); + } + sb.append(")"); + return sb.toString(); + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/CustomTypesConfiguration.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/CustomTypesConfiguration.java index e28ecc2..1c75ac8 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/CustomTypesConfiguration.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/CustomTypesConfiguration.java @@ -14,23 +14,6 @@ public final class CustomTypesConfiguration { this.javaClass = javaClass; } - public TypeName getJavaClassType() { - int indexOfGeneric; - if ((indexOfGeneric = javaClass.indexOf("<")) == -1) { - return ClassName.bestGuess(javaClass.trim()); - } else { - var rawTypesArray = javaClass.substring(indexOfGeneric + 1, javaClass.length() - 1).split(","); - var genericsResult = new TypeName[rawTypesArray.length]; - int i = 0; - for (String rawType : rawTypesArray) { - genericsResult[i] = ClassName.bestGuess(rawType.trim()); - i++; - } - var base = ClassName.bestGuess(javaClass.substring(0, indexOfGeneric).trim()); - return ParameterizedTypeName.get(base, genericsResult); - } - } - public String getJavaClassString() { return javaClass; } 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 f9f3c41..49ee29a 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 @@ -3,20 +3,14 @@ package it.cavallium.data.generator.plugin; import static java.util.Objects.requireNonNull; import static java.util.function.Function.identity; -import it.cavallium.data.generator.plugin.ComputedType.ComputedArrayType; -import it.cavallium.data.generator.plugin.ComputedType.ComputedBaseType; -import it.cavallium.data.generator.plugin.ComputedType.ComputedCustomType; -import it.cavallium.data.generator.plugin.ComputedType.ComputedNativeType; -import it.cavallium.data.generator.plugin.ComputedType.ComputedNullableType; -import it.cavallium.data.generator.plugin.ComputedType.ComputedSuperType; import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectCollection; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; @@ -30,6 +24,7 @@ import java.util.concurrent.atomic.LongAdder; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; @@ -47,7 +42,7 @@ public class DataModel { "Int52" ); - private final int currentVersion; + private final ComputedVersion currentVersion; private final int hash; private final Map interfacesData; private final Int2ObjectMap versions; @@ -107,7 +102,6 @@ public class DataModel { .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); // Build versions sequence - List rawVersionsSequence = new ArrayList<>(); int versionsCount = 0; Int2ObjectMap versionToName = new Int2ObjectLinkedOpenHashMap<>(); Object2IntMap nameToVersion = new Object2IntOpenHashMap<>(); @@ -115,7 +109,6 @@ public class DataModel { String lastVersion = null; String nextVersion = rootVersion; while (nextVersion != null) { - rawVersionsSequence.add(nextVersion); lastVersion = nextVersion; versionToName.put(versionsCount, nextVersion); nameToVersion.put(nextVersion, versionsCount); @@ -157,8 +150,8 @@ public class DataModel { }); // Compute the numeric versions map - Int2ObjectMap versions = new Int2ObjectLinkedOpenHashMap<>(); - rawVersions.forEach((k, v) -> versions.put(nameToVersion.getInt(k), new ParsedVersion(v))); + Int2ObjectMap parsedVersions = new Int2ObjectLinkedOpenHashMap<>(); + rawVersions.forEach((k, v) -> parsedVersions.put(nameToVersion.getInt(k), new ParsedVersion(v))); Int2ObjectMap> computedClassConfig = new Int2ObjectLinkedOpenHashMap<>(); for (int versionIndex = 0; versionIndex < versionsCount; versionIndex++) { @@ -168,7 +161,7 @@ public class DataModel { .collect(Collectors.toMap(Entry::getKey, Entry::getValue)) ); } else { - var version = versions.get(versionIndex); + var version = parsedVersions.get(versionIndex); Map prevVersionConfiguration = requireNonNull(computedClassConfig.get(versionIndex - 1)); Map newVersionConfiguration = prevVersionConfiguration.entrySet().stream() @@ -194,7 +187,7 @@ public class DataModel { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.transformClass); } - transformClass.changed = true; + transformClass.differentThanPrev = true; var definition = removeAndGetIndex(transformClass.data, t.from); if (definition.isEmpty()) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown field: " + t.from); @@ -217,7 +210,7 @@ public class DataModel { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.transformClass); } - transformClass.changed = true; + transformClass.differentThanPrev = true; if (!allTypes.contains(extractTypeName(t.type))) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.type); } @@ -240,7 +233,7 @@ public class DataModel { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.transformClass); } - transformClass.changed = true; + transformClass.differentThanPrev = true; var prevDef = transformClass.data.remove(t.from); if (prevDef == null) { throw new IllegalArgumentException(transformCoordinate + " tries to remove the nonexistent field \"" @@ -254,7 +247,7 @@ public class DataModel { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.transformClass); } - transformClass.changed = true; + transformClass.differentThanPrev = true; if (!allTypes.contains(extractTypeName(t.type))) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.type); } @@ -271,84 +264,141 @@ public class DataModel { } } + // Compute the versions + var computedVersions = parsedVersions + .int2ObjectEntrySet() + .stream() + .collect(Collectors.toMap(Int2ObjectMap.Entry::getIntKey, + e -> new ComputedVersion(e.getValue(), + e.getIntKey(), + e.getIntKey() == latestVersion, + versionToName.get(e.getIntKey()) + ), + (a, b) -> { + throw new IllegalStateException(); + }, + Int2ObjectLinkedOpenHashMap::new + )); + // Compute the types Int2ObjectMap> computedTypes = new Int2ObjectLinkedOpenHashMap<>(); - ComputedTypeSupplier computedTypeSupplier = new ComputedTypeSupplier(computedTypes); - for (int versionIndex = 0; versionIndex < versionsCount; versionIndex++) { - int versionIndexF = versionIndex; - if (versionIndexF == 0) { - // Compute base types - List versionBaseTypes = computedClassConfig.get(versionIndexF).entrySet().stream() - .map(e -> { - var data = new LinkedHashMap(); - e.getValue().getData().forEach((key, value) -> data.put(key, new VersionedType(value, versionIndexF))); - return new ComputedBaseType(new VersionedType(e.getKey(), versionIndexF), - e.getValue().stringRepresenter, data, computedTypeSupplier); - }).collect(Collectors.toList()); - // Compute custom types - customTypesData.forEach((name, data) -> versionBaseTypes.add(new ComputedCustomType(name, - data.getJavaClassString(), data.serializer, computedTypeSupplier))); - // Compute super types - superTypesData.forEach((key, data) -> { - List subTypes = data.stream().map(x -> new VersionedType(x, versionIndexF)).toList(); - versionBaseTypes.add(new ComputedSuperType(new VersionedType(key, versionIndexF), subTypes, computedTypeSupplier)); - }); - // Compute nullable types - computedClassConfig.values().stream() - .flatMap(x -> x.values().stream()) - .flatMap(x -> x.getData().values().stream()) - .filter(x -> x.startsWith("-")) - .map(nullableName -> new VersionedType(nullableName.substring(1), versionIndexF)) - .map(baseType -> new ComputedNullableType(baseType, computedTypeSupplier)) - .forEach(versionBaseTypes::add); - // Compute array types - computedClassConfig.values().stream() - .flatMap(x -> x.values().stream()) - .flatMap(x -> x.getData().values().stream()) - .filter(x -> x.startsWith("§")) - .map(nullableName -> new VersionedType(nullableName.substring(1), versionIndexF)) - .map(baseType -> new ComputedArrayType(baseType, computedTypeSupplier)) - .forEach(versionBaseTypes::add); - // Compute native types - versionBaseTypes.addAll(ComputedNativeType.get(computedTypeSupplier)); - - computedTypes.put(versionIndexF, - versionBaseTypes.stream().distinct().collect(Collectors.toMap(ComputedType::getName, identity()))); - } else { - Set changedTypes = computedTypes.get(versionIndexF - 1).values().stream() - .filter(prevType -> prevType instanceof ComputedBaseType prevBaseType - && computedClassConfig.get(versionIndexF).get(prevBaseType.getName()).changed) - .map(ComputedType::getName) - .collect(Collectors.toSet()); - - { - boolean addedMoreTypes; - do { - var newChangedTypes = changedTypes - .parallelStream() - .flatMap(changedType -> computedTypes.get(versionIndexF - 1).get(changedType).getDependents()) - .map(ComputedType::getName) - .distinct() - .toList(); - addedMoreTypes = changedTypes.addAll(newChangedTypes); - } while (addedMoreTypes); - } - - Map currentVersionComputedTypes = new HashMap<>(); - var versionChangeChecker = new VersionChangeChecker(changedTypes); - computedTypes.get(versionIndexF - 1).forEach((name, type) -> { - if (!changedTypes.contains(name)) { - currentVersionComputedTypes.put(name, type); - } else { - if (type instanceof VersionedComputedType versionedComputedType) { - ComputedType newType = versionedComputedType.withChangeAtVersion(versionIndexF, versionChangeChecker); - currentVersionComputedTypes.put(name, newType); - } else { - throw new IllegalStateException(); - } + Int2ObjectMap> randomComputedTypes = new Int2ObjectOpenHashMap<>(); + ComputedTypeSupplier computedTypeSupplier = new ComputedTypeSupplier(randomComputedTypes, computedVersions.get(latestVersion)); + { + for (int versionNumber = latestVersion - 1; versionNumber >= 0; versionNumber--) { + var version = computedClassConfig.get(versionNumber); + computedClassConfig.get(versionNumber + 1).forEach((type, typeConfig) -> { + if (typeConfig.differentThanPrev) { + version.get(type).differentThanNext = true; } }); - computedTypes.put(versionIndexF, currentVersionComputedTypes); + } + for (int versionIndex = latestVersion; versionIndex >= 0; versionIndex--) { + int versionIndexF = versionIndex; + var version = computedVersions.get(versionIndexF); + if (versionIndexF == latestVersion) { + // Compute base types + List versionBaseTypes = computedClassConfig.get(versionIndexF).entrySet().stream() + .map(e -> { + var data = new LinkedHashMap(); + e.getValue().getData().forEach((key, value) -> data.put(key, new VersionedType(value, version))); + return new ComputedTypeBase(new VersionedType(e.getKey(), version), + e.getValue().stringRepresenter, data, computedTypeSupplier); + }).collect(Collectors.toList()); + // Compute custom types + customTypesData.forEach((name, data) -> versionBaseTypes.add(new ComputedTypeCustom(name, + data.getJavaClassString(), data.serializer, computedTypeSupplier, computedVersions.get(latestVersion)))); + // Compute super types + superTypesData.forEach((key, data) -> { + List subTypes = data.stream().map(x -> new VersionedType(x, version)).toList(); + versionBaseTypes.add(new ComputedTypeSuper(new VersionedType(key, version), subTypes, computedTypeSupplier)); + }); + // Compute nullable types + { + var nullableRawTypes = computedClassConfig.values().stream() + .flatMap(x -> x.values().stream()) + .flatMap(x -> x.getData().values().stream()) + .filter(x -> x.startsWith("-")) + .map(nullableName -> nullableName.substring(1)) + .toList(); + // Compute nullable base types + nullableRawTypes.stream() + .filter(nullableName -> !NATIVE_TYPES.contains(nullableName)) + .map(nullableName -> new VersionedType(nullableName, version)) + .map(baseType -> new ComputedTypeNullableVersioned(baseType, computedTypeSupplier)) + .forEach(versionBaseTypes::add); + // Compute nullable native types + nullableRawTypes.stream() + .filter(NATIVE_TYPES::contains) + .map(baseType -> + new ComputedTypeNullableNative(baseType, computedVersions.get(latestVersion), computedTypeSupplier)) + .forEach(versionBaseTypes::add); + } + // Compute array types + { + var arrayRawTypes = computedClassConfig.values().stream() + .flatMap(x -> x.values().stream()) + .flatMap(x -> x.getData().values().stream()) + .filter(x -> x.startsWith("§")) + .map(nullableName -> nullableName.substring(1)) + .toList(); + // Compute array base types + arrayRawTypes.stream() + .filter(nullableName -> !NATIVE_TYPES.contains(nullableName)) + .map(nullableName -> new VersionedType(nullableName, version)) + .map(baseType -> new ComputedTypeArrayVersioned(baseType, computedTypeSupplier)) + .forEach(versionBaseTypes::add); + // Compute array native types + arrayRawTypes.stream() + .filter(NATIVE_TYPES::contains) + .map(baseType -> + new ComputedTypeArrayNative(baseType, computedTypeSupplier)) + .forEach(versionBaseTypes::add); + } + // Compute native types + versionBaseTypes.addAll(ComputedTypeNative.get(computedTypeSupplier)); + + randomComputedTypes.put(versionIndexF, + versionBaseTypes.stream().distinct().collect(Collectors.toMap(ComputedType::getName, identity()))); + } else { + Set changedTypes = randomComputedTypes.get(versionIndexF + 1).values().stream() + .filter(prevType -> prevType instanceof ComputedTypeBase prevBaseType + && computedClassConfig.get(versionIndexF).get(prevBaseType.getName()).differentThanNext) + .map(ComputedType::getName) + .collect(Collectors.toSet()); + + { + boolean addedMoreTypes; + do { + var newChangedTypes = changedTypes + .parallelStream() + .flatMap(changedType -> randomComputedTypes.get(versionIndexF + 1).get(changedType).getDependents()) + .map(ComputedType::getName) + .distinct() + .toList(); + addedMoreTypes = changedTypes.addAll(newChangedTypes); + } while (addedMoreTypes); + } + + Map currentVersionComputedTypes = new HashMap<>(); + var versionChangeChecker = new VersionChangeChecker(changedTypes, versionIndexF, latestVersion); + randomComputedTypes.get(versionIndexF + 1).forEach((name, type) -> { + if (!changedTypes.contains(name)) { + currentVersionComputedTypes.put(name, type); + } else { + if (type instanceof VersionedComputedType versionedComputedType) { + ComputedType newType = versionedComputedType.withChangeAtVersion(version, versionChangeChecker); + currentVersionComputedTypes.put(name, newType); + } else { + throw new IllegalStateException(); + } + } + }); + randomComputedTypes.put(versionIndexF, currentVersionComputedTypes); + } + } + for (int i = 0; i < versionsCount; i++) { + computedTypes.put(i, Objects.requireNonNull(randomComputedTypes.get(i))); } } // All types, including arrays, nullables, primitives, etc @@ -366,7 +416,7 @@ public class DataModel { .collect(Collectors.toMap(Entry::getKey, e -> e .getValue() .stream() - .sorted(Comparator.comparingInt(VersionedComputedType::getVersion)) + .sorted(Comparator.comparingInt(x -> x.getVersion().getVersion())) .map(x -> new VersionedType(e.getKey(), x.getVersion())) .toList())); versionedTypeVersions.forEach((type, versionsList) -> { @@ -434,43 +484,31 @@ public class DataModel { |__BaseGroupId v1 */ + this.versions = computedVersions; this.interfacesData = interfacesData.entrySet().stream() .map(e -> Map.entry(e.getKey(), new ParsedInterface(e.getValue()))) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); - this.versions = versions - .int2ObjectEntrySet() - .stream() - .collect(Collectors.toMap(Int2ObjectMap.Entry::getIntKey, - e -> new ComputedVersion(e.getValue(), - e.getIntKey(), - e.getIntKey() == latestVersion, - versionToName.get(e.getIntKey()), - computedClassConfig.get(e.getIntKey()) - ), - (a, b) -> { - throw new IllegalStateException(); - }, - Int2ObjectLinkedOpenHashMap::new - )); LongAdder unchangedTot = new LongAdder(); LongAdder changedTot = new LongAdder(); computedTypes.forEach((version, types) -> { System.out.println("Version: " + version); System.out.println("\tTypes: " + types.size()); System.out.println("\tVersioned types: " + types.values().stream().filter(t -> (t instanceof VersionedComputedType)).count()); - var unchanged = types.values().stream().filter(t -> (t instanceof VersionedComputedType versionedComputedType && versionedComputedType.getVersion() < version)).count(); - var changed = types.values().stream().filter(t -> (t instanceof VersionedComputedType versionedComputedType && versionedComputedType.getVersion() == version)).count(); + var unchanged = types.values().stream().filter(t -> (t instanceof VersionedComputedType versionedComputedType + && versionedComputedType.getVersion().getVersion() != version)).count(); + var changed = types.values().stream().filter(t -> (t instanceof VersionedComputedType versionedComputedType + && versionedComputedType.getVersion().getVersion() == version)).count(); unchangedTot.add(unchanged); changedTot.add(changed); - System.out.println("\t\tUnchanged: " + unchanged + " (" + (unchanged * 100 / (changed + unchanged)) + "%)"); - System.out.println("\t\tChanged: " + changed + " (" + (changed * 100 / (changed + unchanged)) + "%)"); + System.out.println("\t\tUnchanged: " + unchanged + " (" + (unchanged * 100 / Math.max(changed + unchanged, 1)) + "%)"); + System.out.println("\t\tChanged: " + changed + " (" + (changed * 100 / Math.max(changed + unchanged, 1)) + "%)"); }); System.out.println("Result:"); var unchanged = unchangedTot.sum(); var changed = changedTot.sum(); System.out.println("\tAvoided type versions: " + unchanged + " (" + (unchanged * 100 / (changed + unchanged)) + "%)"); System.out.println("\tType versions: " + changed + " (" + (changed * 100 / (changed + unchanged)) + "%)"); - this.currentVersion = versionsCount - 1; + this.currentVersion = computedVersions.get(versionsCount - 1); this.superTypes = superTypesData; this.customTypes = customTypesData; this.computedTypes = computedTypes; @@ -604,17 +642,36 @@ public class DataModel { } public int getCurrentVersionNumber() { - return currentVersion; + return currentVersion.getVersion(); } public ComputedVersion getCurrentVersion() { - return versions.get(currentVersion); + return currentVersion; } - public Map> getSuperTypes() { + @Deprecated + public Map> getSuperTypesRaw() { return this.superTypes; } + public Stream getSuperTypesComputed() { + return getSuperTypesComputed(currentVersion); + } + + public Stream getSuperTypesComputed(ComputedVersion version) { + return this.computedTypes.get(version.getVersion()).values().stream() + .filter(t -> t instanceof ComputedTypeSuper).map(t -> (ComputedTypeSuper) t); + } + + public Stream getBaseTypesComputed() { + return getBaseTypesComputed(currentVersion); + } + + public Stream getBaseTypesComputed(ComputedVersion version) { + return this.computedTypes.get(version.getVersion()).values().stream() + .filter(t -> t instanceof ComputedTypeBase).map(t -> (ComputedTypeBase) t); + } + public Optional getNextVersion(ComputedVersion versionConfiguration) { var nextVersion = versions.get(versionConfiguration.getVersion() + 1); return Optional.ofNullable(nextVersion); @@ -628,6 +685,7 @@ public class DataModel { return interfacesData; } + @Deprecated public Map getCustomTypes() { return customTypes; } @@ -636,6 +694,10 @@ public class DataModel { return computedTypes; } + public Map getComputedTypes(ComputedVersion version) { + return computedTypes.get(version.getVersion()); + } + public VersionedType getNextVersion(VersionedType type) { return versionedTypeNextVersion.get(type); } @@ -644,27 +706,134 @@ public class DataModel { return versionedTypePrevVersion.get(type); } - public ComputedType getNextVersion(ComputedType type) { + public T getNextVersion(T type) { if (type instanceof VersionedComputedType versionedComputedType) { var result = versionedTypeNextVersion.get(new VersionedType(versionedComputedType.getName(), versionedComputedType.getVersion())); if (result == null) { return null; } - return this.computedTypes.get(result.version()).get(result.type()); + //noinspection unchecked + return (T) this.computedTypes.get(result.version().getVersion()).get(result.type()); } else { return null; } } - public ComputedType getPrevVersion(ComputedType type) { + public T getPrevVersion(T type) { if (type instanceof VersionedComputedType versionedComputedType) { var result = versionedTypePrevVersion.get(new VersionedType(versionedComputedType.getName(), versionedComputedType.getVersion())); if (result == null) { return null; } - return this.computedTypes.get(result.version()).get(result.type()); + //noinspection unchecked + return (T) this.computedTypes.get(result.version().getVersion()).get(result.type()); } else { return null; } } + + public boolean isTypeForVersion(ComputedVersion versionConfiguration, String key) { + var type = getComputedTypes(versionConfiguration).get(key); + return type instanceof VersionedComputedType versionedComputedType + && versionedComputedType.getVersion().getVersion() == versionConfiguration.getVersion(); + } + + public ComputedVersion getTypeFirstSameVersion(VersionedComputedType type) { + var prevVersion = getPrevVersion(type); + if (prevVersion != null) { + return versions.get(prevVersion.getVersion().getVersion() + 1); + } else { + return type.getVersion(); + } + } + + public Stream getTypeSameVersions(VersionedComputedType type) { + var initialVersion = getTypeFirstSameVersion(type); + var lastVersion = type.getVersion(); + return getVersionRange(initialVersion, lastVersion); + } + + @Deprecated + public ComputedVersion getVersion(int version) { + return Objects.requireNonNull(versions.get(version)); + } + + public ComputedVersion getVersion(VersionedComputedType versionedComputedType) { + return Objects.requireNonNull(versions.get(versionedComputedType.getVersion().getVersion())); + } + + public Stream getVersionRange(ComputedVersion initialVersionInclusive, + ComputedVersion lastVersionInclusive) { + if (initialVersionInclusive.getVersion() > lastVersionInclusive.getVersion()) { + throw new IllegalArgumentException(); + } + return IntStream + .rangeClosed(initialVersionInclusive.getVersion(), lastVersionInclusive.getVersion()) + .mapToObj(versions::get); + } + + public String getVersionPackage(ComputedVersion version, String basePackageName) { + return version.getPackage(basePackageName); + } + + public String getVersionDataPackage(ComputedVersion version, String basePackageName) { + return version.getDataPackage(basePackageName); + } + + @Deprecated + public String getVersionDataPackage(VersionedComputedType type, String basePackageName) { + return type.getVersion().getDataPackage(basePackageName); + } + + public String getRootPackage(String basePackageName) { + return joinPackage(basePackageName, ""); + } + + public static String joinPackage(String basePackageName, String packageName) { + if (basePackageName.isBlank()) { + basePackageName = "org.generated"; + } + if (packageName.isBlank()) { + return basePackageName; + } else { + return basePackageName + "." + packageName; + } + } + + public Stream getSuperTypesOf(VersionedComputedType baseType) { + return getSuperTypesComputed(baseType.getVersion()).filter(type -> type.subTypes().contains(baseType)); + } + + public Stream getExtendsInterfaces(ComputedTypeSuper superType) { + if (superType.getVersion().isCurrent()) { + var interfaces = interfacesData.get(superType.getName()); + if (interfaces != null) { + return interfaces.extendInterfaces.stream() + .map(name -> (ComputedTypeSuper) this.computedTypes.get(currentVersion.getVersion()).get(name)); + } + } + return Stream.of(); + } + + public Stream> getCommonInterfaceGetters(ComputedTypeSuper superType) { + if (superType.getVersion().isCurrent()) { + var interfaces = interfacesData.get(superType.getName()); + if (interfaces != null) { + return interfaces.commonGetters.entrySet().stream().map(x -> + Map.entry(x.getKey(), this.computedTypes.get(currentVersion.getVersion()).get(x.getValue()))); + } + } + return Stream.of(); + } + + public Stream> getCommonInterfaceData(ComputedTypeSuper superType) { + if (superType.getVersion().isCurrent()) { + var interfaces = interfacesData.get(superType.getName()); + if (interfaces != null) { + return interfaces.commonData.entrySet().stream().map(x -> + Map.entry(x.getKey(), this.computedTypes.get(currentVersion.getVersion()).get(x.getValue()))); + } + } + return Stream.of(); + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/FieldLocation.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/FieldLocation.java new file mode 100644 index 0000000..e5b6ee8 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/FieldLocation.java @@ -0,0 +1,5 @@ +package it.cavallium.data.generator.plugin; + +import com.squareup.javapoet.TypeName; + +public record FieldLocation(TypeName className, String fieldName) {} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/MavenPlugin.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/MavenPlugin.java index e2e5bf1..8dd084a 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/MavenPlugin.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/MavenPlugin.java @@ -20,6 +20,9 @@ public class MavenPlugin extends AbstractMojo { @Parameter( required = true) private String basePackageName; + @Parameter( required = true, defaultValue = "true") + private String deepCheckBeforeCreatingNewEqualInstances; + @Parameter( required = true, defaultValue = "false") private String useRecordBuilder; @@ -39,7 +42,7 @@ public class MavenPlugin extends AbstractMojo { Path outPath = genRecordsPath.resolve("java"); this.project.addCompileSourceRoot(outPath.toString()); - sourcesGenerator.generateSources(basePackageName, outPath, Boolean.parseBoolean(useRecordBuilder)); + sourcesGenerator.generateSources(basePackageName, outPath, Boolean.parseBoolean(useRecordBuilder), false, Boolean.parseBoolean(deepCheckBeforeCreatingNewEqualInstances)); } catch (IOException e) { throw new MojoExecutionException("Exception while generating classes", e); } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ParsedClass.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ParsedClass.java index 659890f..604468e 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ParsedClass.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ParsedClass.java @@ -2,7 +2,6 @@ package it.cavallium.data.generator.plugin; import static it.cavallium.data.generator.plugin.DataModel.fixType; -import it.unimi.dsi.fastutil.objects.Object2IntMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; @@ -14,7 +13,8 @@ public final class ParsedClass { public String stringRepresenter; public LinkedHashMap data; - public boolean changed; + public boolean differentThanPrev; + public boolean differentThanNext; public ParsedClass(ClassConfiguration baseTypesData) { this.stringRepresenter = baseTypesData.stringRepresenter; 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 b429529..25d990a 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 @@ -1,28 +1,32 @@ package it.cavallium.data.generator.plugin; -import static java.nio.file.StandardOpenOption.*; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static java.nio.file.StandardOpenOption.WRITE; -import com.squareup.javapoet.ArrayTypeName; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeSpec.Builder; -import com.squareup.javapoet.TypeVariableName; -import com.squareup.javapoet.WildcardTypeName; -import io.soabase.recordbuilder.core.RecordBuilder; -import it.cavallium.data.generator.CommonField; -import it.cavallium.data.generator.DataInitializer; -import it.cavallium.data.generator.DataSerializer; -import it.cavallium.data.generator.DataUpgrader; -import it.cavallium.data.generator.nativedata.INullable; -import it.cavallium.data.generator.nativedata.Int52Serializer; -import it.cavallium.data.generator.nativedata.StringSerializer; +import it.cavallium.data.generator.plugin.ClassGenerator.ClassGeneratorParams; +import it.cavallium.data.generator.plugin.classgen.GenBaseType; +import it.cavallium.data.generator.plugin.classgen.GenCurrentVersion; +import it.cavallium.data.generator.plugin.classgen.GenDataBaseX; +import it.cavallium.data.generator.plugin.classgen.GenDataSuperX; +import it.cavallium.data.generator.plugin.classgen.GenIBaseType; +import it.cavallium.data.generator.plugin.classgen.GenINullableBaseType; +import it.cavallium.data.generator.plugin.classgen.GenINullableIType; +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.GenSuperType; +import it.cavallium.data.generator.plugin.classgen.GenVersion; +import it.cavallium.data.generator.plugin.classgen.GenVersions; import it.unimi.dsi.fastutil.booleans.BooleanList; import it.unimi.dsi.fastutil.bytes.ByteList; import it.unimi.dsi.fastutil.chars.CharList; @@ -30,41 +34,25 @@ import it.unimi.dsi.fastutil.doubles.DoubleList; 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.objects.Object2IntLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.shorts.ShortList; import java.io.DataInput; import java.io.DataOutput; import java.io.File; -import java.io.IOError; import java.io.IOException; import java.io.InputStream; -import java.io.Serializable; -import java.lang.reflect.Array; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.Objects; -import java.util.Optional; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.lang.model.element.Modifier; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; @@ -103,8 +91,10 @@ public class SourcesGenerator { * @param basePackageName org.example * @param outPath path/to/output * @param useRecordBuilders if true, the data will have @RecordBuilder annotation + * @param force force overwrite + * @param deepCheckBeforeCreatingNewEqualInstances if true, use equals, if false, use == */ - public void generateSources(String basePackageName, Path outPath, boolean useRecordBuilders) throws IOException { + public void generateSources(String basePackageName, Path outPath, boolean useRecordBuilders, boolean force, boolean deepCheckBeforeCreatingNewEqualInstances) throws IOException { Path basePackageNamePath; { Path basePackageNamePathPartial = outPath; @@ -122,8 +112,10 @@ public class SourcesGenerator { var prevRecordBuilders = lines.get(1); var prevHash = lines.get(2); - if (prevBasePackageName.equals(basePackageName) && (prevRecordBuilders.equalsIgnoreCase("true") == useRecordBuilders) - && prevHash.equals(curHash)) { + if (!force + && prevBasePackageName.equals(basePackageName) + && (prevRecordBuilders.equalsIgnoreCase("true") == useRecordBuilders) + && prevHash.equals(Integer.toString(curHash))) { logger.info("Skipped sources generation because it didn't change"); return; } @@ -148,2215 +140,45 @@ public class SourcesGenerator { .collect(Collectors.toCollection(HashSet::new)); } - // Update the hash - Files.writeString(hashPath, basePackageName + '\n' + useRecordBuilders + '\n' + curHash + '\n', - StandardCharsets.UTF_8, TRUNCATE_EXISTING, WRITE, CREATE); - markFileAsCreated(generatedFilesToDelete, outPath, hashPath); + var genParams = new ClassGeneratorParams(generatedFilesToDelete, dataModel, basePackageName, outPath, deepCheckBeforeCreatingNewEqualInstances); // Create the Versions class - var versionsClass = TypeSpec.classBuilder("Versions"); - versionsClass.addModifiers(Modifier.PUBLIC); - versionsClass.addModifiers(Modifier.FINAL); - var versionsInstances = FieldSpec.builder(ArrayTypeName.of(ClassName.get(joinPackage(basePackageName, ""), "IVersion")), - "VERSIONS", - Modifier.PUBLIC, - Modifier.STATIC, - Modifier.FINAL - ); - List versionsInstancesValue = new ArrayList<>(); - for (ComputedVersion value : dataModel.getVersionsSet()) { - // Add a static variable for this version, containing the normalized version number - var versionNumberField = FieldSpec - .builder(TypeName.INT, getVersionVarName(value)) - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.STATIC) - .addModifiers(Modifier.FINAL) - .initializer(getVersionShortInt(value)) - .build(); - // Add the fields to the class - versionsClass.addField(versionNumberField); - - var versionPackage = value.getPackage(basePackageName); - var versionClassType = ClassName.get(joinPackage(versionPackage, ""), "Version"); - - versionsInstancesValue.add(CodeBlock.builder().add("$T.INSTANCE", versionClassType).build()); - } - versionsInstances.initializer(CodeBlock - .builder() - .add("{\n") - .add(CodeBlock.join(versionsInstancesValue, ",\n")) - .add("\n}") - .build()); - versionsClass.addField(versionsInstances.build()); - // Save the resulting class in the main package - writeClass(generatedFilesToDelete, outPath, joinPackage(basePackageName, ""), versionsClass); + new GenVersions(genParams).run(); // Create the BaseType class - { - var baseTypeClass = TypeSpec.enumBuilder("BaseType"); - baseTypeClass.addModifiers(Modifier.PUBLIC); - for (var value : dataModel.getVersionsSet()) { - for (String baseTypeName : value.getClassMap().keySet()) { - if (!baseTypeClass.enumConstants.containsKey(baseTypeName)) { - baseTypeClass.addEnumConstant(baseTypeName); - } - } - } - // Save the resulting class in the main package - writeClass(generatedFilesToDelete, outPath, joinPackage(basePackageName, ""), baseTypeClass); - } + new GenBaseType(genParams).run(); // Create the SuperType class - { - var superTypeClass = TypeSpec.enumBuilder("SuperType"); - superTypeClass.addModifiers(Modifier.PUBLIC); - for (var value : dataModel.getVersionsSet()) { - for (String superTypeName : dataModel.getSuperTypes().keySet()) { - if (!superTypeClass.enumConstants.containsKey(superTypeName)) { - superTypeClass.addEnumConstant(superTypeName); - } - } - } - // Save the resulting class in the main package - writeClass(generatedFilesToDelete, outPath, joinPackage(basePackageName, ""), superTypeClass); - } + new GenSuperType(genParams).run(); // Create the IVersion class - { - var iVersionClass = TypeSpec.interfaceBuilder("IVersion"); - 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(joinPackage(basePackageName, ""), "BaseType"), "type") - .build()); - iVersionClass.addMethod(getClassMethodBuilder.build()); - } - - // Add getSerializer method - { - var getSerializerMethodBuilder = MethodSpec - .methodBuilder("getSerializer") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.ABSTRACT) - .addTypeVariable(TypeVariableName.get("T", - TypeVariableName.get("B") - )) - .returns(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), TypeVariableName.get("T"))) - .addException(IOException.class) - .addParameter(ParameterSpec - .builder(ClassName.get(joinPackage(basePackageName, ""), "BaseType"), "type") - .build()); - iVersionClass.addMethod(getSerializerMethodBuilder.build()); - } - - // Add getVersion method - { - var getVersionMethod = MethodSpec - .methodBuilder("getVersion") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.ABSTRACT) - .returns(TypeName.INT) - .build(); - iVersionClass.addMethod(getVersionMethod); - } - - // Save the resulting class in the main package - writeClass(generatedFilesToDelete, outPath, joinPackage(basePackageName, ""), iVersionClass); - } - - var currentVersionPackage = dataModel.getCurrentVersion().getPackage(basePackageName); + new GenIVersion(genParams).run(); // Create the CurrentVersion class - { - var currentVersionClass = TypeSpec.classBuilder("CurrentVersion"); - currentVersionClass.addModifiers(Modifier.PUBLIC); - currentVersionClass.addModifiers(Modifier.FINAL); - // Add a static variable for the current version - { - var versionNumberField = FieldSpec.builder(ClassName - .get(dataModel.getCurrentVersion().getPackage(basePackageName), - "Version"), "VERSION").addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC) - .addModifiers(Modifier.FINAL).initializer("new " + dataModel.getCurrentVersion().getPackage(basePackageName) - + ".Version()").build(); - currentVersionClass.addField(versionNumberField); - } - // Check latest version method - { - var isLatestVersionMethod = MethodSpec.methodBuilder("isLatestVersion").addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC).returns(TypeName.BOOLEAN) - .addParameter(ParameterSpec.builder(TypeName.INT, "version").build()) - .addCode("return version == VERSION.getVersion();").build(); - currentVersionClass.addMethod(isLatestVersionMethod); - } - // Get super type classes method - { - var getSuperTypeClasses = MethodSpec.methodBuilder("getSuperTypeClasses").addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC).returns(ParameterizedTypeName.get( - ClassName.get(Set.class), ParameterizedTypeName.get( - ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName - .get(joinPackage(currentVersionPackage, "data"), - "IType"))))) - .addCode("return $T.of(\n", Set.class); - AtomicBoolean isFirst = new AtomicBoolean(true); - for (Entry> entry : dataModel.getSuperTypes().entrySet()) { - String superTypeName = entry.getKey(); - Set superTypeConfig = entry.getValue(); - if (!isFirst.getAndSet(false)) { - getSuperTypeClasses.addCode(",\n"); - } - getSuperTypeClasses.addCode("$T.class", - ClassName.get(joinPackage(currentVersionPackage, "data"), superTypeName) - ); - } - getSuperTypeClasses.addCode("\n);"); - currentVersionClass.addMethod(getSuperTypeClasses.build()); - } - // Get super type subtypes classes method - { - var getSuperTypeSubtypesClasses = MethodSpec.methodBuilder("getSuperTypeSubtypesClasses").addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC).returns(ParameterizedTypeName.get( - ClassName.get(Set.class), ParameterizedTypeName.get( - ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName - .get(joinPackage(currentVersionPackage, "data"), - "IBaseType"))))); - getSuperTypeSubtypesClasses.addParameter(ParameterSpec.builder(ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName - .get(joinPackage(currentVersionPackage, "data"), - "IType"))), "superTypeClass").build()); - getSuperTypeSubtypesClasses.beginControlFlow("switch (superTypeClass.getCanonicalName())"); - for (Entry> entry : dataModel.getSuperTypes().entrySet()) { - String superTypeName = entry.getKey(); - Set subTypes = entry.getValue(); - getSuperTypeSubtypesClasses.beginControlFlow("case \"" + ClassName - .get(joinPackage(currentVersionPackage, "data"), superTypeName) - .canonicalName() + "\":"); - getSuperTypeSubtypesClasses.addCode("return $T.of(\n", Set.class); - AtomicBoolean isFirst = new AtomicBoolean(true); - for (String subTypeName : subTypes) { - if (!isFirst.getAndSet(false)) { - getSuperTypeSubtypesClasses.addCode(",\n"); - } - getSuperTypeSubtypesClasses.addCode("$T.class", - ClassName.get(joinPackage(currentVersionPackage, "data"), subTypeName) - ); - } - getSuperTypeSubtypesClasses.addCode("\n);\n"); - getSuperTypeSubtypesClasses.endControlFlow(); - } - getSuperTypeSubtypesClasses.beginControlFlow("default:"); - getSuperTypeSubtypesClasses.addStatement("throw new $T()", IllegalArgumentException.class); - getSuperTypeSubtypesClasses.endControlFlow(); - getSuperTypeSubtypesClasses.endControlFlow(); - currentVersionClass.addMethod(getSuperTypeSubtypesClasses.build()); - } - // UpgradeDataToLatestVersion1 Method - { - var upgradeDataToLatestVersion1MethodBuilder = MethodSpec.methodBuilder("upgradeDataToLatestVersion").addTypeVariable(TypeVariableName.get("U", ClassName - .get(joinPackage(currentVersionPackage, "data"), - "IBaseType"))) - .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(joinPackage(basePackageName, ""), "BaseType"), "type").build()) - .addParameter(ParameterSpec.builder(DataInput.class, "oldDataInput").build()) - .addException(IOException.class).beginControlFlow("switch (oldVersion)"); - AtomicInteger seqNumber = new AtomicInteger(0); - for (var versionConfiguration : dataModel.getVersionsSet()) { -// Add a case in which the data version deserializes the serialized data and upgrades it - upgradeDataToLatestVersion1MethodBuilder.beginControlFlow("case $T." + versionConfiguration.getVersionVarName() + ":", - ClassName.get(joinPackage(basePackageName, ""), "Versions") - ); - upgradeDataToLatestVersion1MethodBuilder.addStatement("var deserialized" + seqNumber.incrementAndGet() + " = " - + versionConfiguration.getPackage(basePackageName) - + ".Version.INSTANCE.getSerializer(type).deserialize(oldDataInput)"); - upgradeDataToLatestVersion1MethodBuilder.addStatement( - "return upgradeDataToLatestVersion(Versions." + versionConfiguration.getVersionVarName() + ", deserialized" - + seqNumber.get() + ")"); - upgradeDataToLatestVersion1MethodBuilder.endControlFlow(); - } - var upgradeDataToLatestVersion1Method = upgradeDataToLatestVersion1MethodBuilder.beginControlFlow("default:") - .addStatement("throw new $T(\"Unknown version: \" + oldVersion)", IOException.class).endControlFlow() - .endControlFlow().build(); - currentVersionClass.addMethod(upgradeDataToLatestVersion1Method); - } - // UpgradeDataToLatestVersion2 Method - { - var versionsClassName = ClassName.get(joinPackage(basePackageName, ""), "Versions"); - var upgradeDataToLatestVersion2MethodBuilder = MethodSpec.methodBuilder("upgradeDataToLatestVersion") - .addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC).addModifiers(Modifier.FINAL).addTypeVariable(TypeVariableName.get("T")) - .addTypeVariable(TypeVariableName.get("U", ClassName - .get(joinPackage(currentVersionPackage, "data"), - "IBaseType"))).returns(TypeVariableName.get("U")) - .addParameter(ParameterSpec.builder(TypeName.INT, "oldVersion").build()) - .addParameter(ParameterSpec.builder(TypeVariableName.get("T"), "oldData").build()) - .addException(IOException.class) - .addStatement("int intermediateVersion = oldVersion") - .addStatement("$T intermediateData = oldData", Object.class) - .beginControlFlow("while (true)") - .beginControlFlow("switch (intermediateVersion)"); - for (var versionConfiguration : dataModel.getVersionsSet()) { -// Add a case in which the data version deserializes the serialized data and upgrades it - upgradeDataToLatestVersion2MethodBuilder.beginControlFlow("case $T." + versionConfiguration.getVersionVarName() + ":", - versionsClassName - ); - if (versionConfiguration.isCurrent()) { - // This is the latest version, don't upgrade. - upgradeDataToLatestVersion2MethodBuilder.addStatement("return ($T) intermediateData", TypeVariableName.get("U")); - } else { - // Upgrade - upgradeDataToLatestVersion2MethodBuilder - .addStatement( - "intermediateData = " + versionConfiguration.getPackage(basePackageName) - + ".Version.upgradeToNextVersion(($T) intermediateData)", - ClassName.get(joinPackage(versionConfiguration.getPackage (basePackageName), - "data" - ), "IBaseType") - ) - .addStatement("intermediateVersion = $T." - + getVersionVarName(dataModel.getNextVersionOrThrow(versionConfiguration)), versionsClassName) - .addStatement("break"); - } - upgradeDataToLatestVersion2MethodBuilder.endControlFlow(); - } - var upgradeDataToLatestVersion2Method = upgradeDataToLatestVersion2MethodBuilder.beginControlFlow("default:") - .addStatement("throw new $T(\"Unknown version: \" + oldVersion)", IOException.class).endControlFlow() - .endControlFlow() - .endControlFlow() - .build(); - currentVersionClass.addMethod(upgradeDataToLatestVersion2Method); - } - // Save the resulting class in the main package - writeClass(generatedFilesToDelete, outPath, joinPackage(basePackageName, "current"), currentVersionClass); - } + new GenCurrentVersion(genParams).run(); - for (var versionConfiguration : dataModel.getVersionsSet()) { - var versionPackage = versionConfiguration.getPackage(basePackageName); - var versionClassType = ClassName.get(joinPackage(versionPackage, ""), "Version"); - var nextVersion = dataModel.getNextVersion(versionConfiguration); - var nextVersionPackage = nextVersion.map((nextVersionValue) -> nextVersionValue.getPackage(basePackageName)); + new GenVersion(genParams).run(); - logger.info("Found version configuration:\n{\n\tversion: \"" + versionConfiguration.getName() - + "\",\n\tversionPackage: \"" + versionPackage + "\",\n\tnextVersion: \"" + nextVersion - .map(ComputedVersion::getName) - .orElse("unknown") + "\",\n\tnextVersionPackage: \"" + nextVersionPackage.orElse("unknown") + "\"\n}"); + new GenIBaseType(genParams).run(); - HashMap typeOptionalSerializers = new LinkedHashMap<>(); - HashMap typeOptionalUpgraders = new LinkedHashMap<>(); - HashMap typeSerializeStatement = new HashMap<>(); - HashMap typeDeserializeStatement = new HashMap<>(); - HashMap typeMustGenerateSerializer = new HashMap<>(); - HashMap typeTypes = new LinkedHashMap<>(Map.of("boolean", - TypeName.BOOLEAN, - "short", - TypeName.SHORT, - "char", - TypeName.CHAR, - "int", - TypeName.INT, - "long", - TypeName.LONG, - "float", - TypeName.FLOAT, - "double", - TypeName.DOUBLE, - "byte", - TypeName.BYTE - )); - HashMap typeFamily = new LinkedHashMap<>(Map.of("boolean", - Family.SPECIAL_NATIVE, - "short", - Family.SPECIAL_NATIVE, - "char", - Family.SPECIAL_NATIVE, - "int", - Family.SPECIAL_NATIVE, - "long", - Family.SPECIAL_NATIVE, - "float", - Family.SPECIAL_NATIVE, - "double", - Family.SPECIAL_NATIVE, - "byte", - Family.SPECIAL_NATIVE - )); - @Nullable HashMap nextVersionTypeTypes; - @Nullable HashMap nextVersionTypeFamily; - if (nextVersion.isPresent()) { - nextVersionTypeTypes = new LinkedHashMap<>(Map.of("boolean", - TypeName.BOOLEAN, - "short", - TypeName.SHORT, - "char", - TypeName.CHAR, - "int", - TypeName.INT, - "long", - TypeName.LONG, - "float", - TypeName.FLOAT, - "double", - TypeName.DOUBLE, - "byte", - TypeName.BYTE - )); - nextVersionTypeFamily = new LinkedHashMap<>(Map.of("boolean", - Family.SPECIAL_NATIVE, - "short", - Family.SPECIAL_NATIVE, - "char", - Family.SPECIAL_NATIVE, - "int", - Family.SPECIAL_NATIVE, - "long", - Family.SPECIAL_NATIVE, - "float", - Family.SPECIAL_NATIVE, - "double", - Family.SPECIAL_NATIVE, - "byte", - Family.SPECIAL_NATIVE - )); - } else { - nextVersionTypeTypes = null; - nextVersionTypeFamily = null; - } - Set specialNativeTypes = Set.of("String", - "boolean", - "short", - "char", - "int", - "long", - "float", - "double", - "byte", - "Int52" - ); + new GenIType(genParams).run(); - // Generate the type statements - { - // Generate the native types - for (String specialNativeType : specialNativeTypes) { - if (Character.isUpperCase(specialNativeType.charAt(0))) { - typeTypes.put(specialNativeType, ClassName.get(getSpecialNativePackage(specialNativeType), specialNativeType)); - typeFamily.put(specialNativeType, Family.SPECIAL_NATIVE); - if (nextVersion.isPresent()) { - nextVersionTypeTypes.put(specialNativeType, ClassName.get(getSpecialNativePackage(specialNativeType), specialNativeType)); - nextVersionTypeFamily.put(specialNativeType, Family.SPECIAL_NATIVE); - } - if (specialNativeType.equals("String")) { - typeSerializeStatement.put(specialNativeType, - new SerializeCodeBlockGenerator(CodeBlock - .builder() - .add("$T.INSTANCE.serialize(dataOutput, ", StringSerializer.class) - .build(), CodeBlock.builder().add(")").build()) - ); - typeDeserializeStatement.put(specialNativeType, - CodeBlock.builder().add("$T.INSTANCE.deserialize(dataInput)", StringSerializer.class).build() - ); - } else if (specialNativeType.equals("Int52")) { - typeSerializeStatement.put(specialNativeType, - new SerializeCodeBlockGenerator(CodeBlock - .builder() - .add("$T.INSTANCE.serialize(dataOutput, ", Int52Serializer.class) - .build(), CodeBlock.builder().add(")").build()) - ); - typeDeserializeStatement.put(specialNativeType, - CodeBlock.builder().add("$T.INSTANCE.deserialize(dataInput)", Int52Serializer.class).build() - ); - } else { - typeSerializeStatement.put(specialNativeType, - new SerializeCodeBlockGenerator(CodeBlock - .builder() - .add("dataOutput.write" + specialNativeType + "(") - .build(), CodeBlock.builder().add(")").build()) - ); - typeDeserializeStatement.put(specialNativeType, - CodeBlock.builder().add("dataInput.read" + specialNativeType + "()").build() - ); - } - } else { - var uppercasedType = Character.toUpperCase(specialNativeType.charAt(0)) + specialNativeType.substring(1); - // don't put the special type, because it's already in the hashmap. - typeSerializeStatement.put(specialNativeType, - new SerializeCodeBlockGenerator(CodeBlock - .builder() - .add("dataOutput.write" + uppercasedType + "(") - .build(), CodeBlock.builder().add(")").build()) - ); - typeDeserializeStatement.put(specialNativeType, - CodeBlock.builder().add("dataInput.read" + uppercasedType + "()").build() - ); - } - typeMustGenerateSerializer.put(specialNativeType, false); + new GenNullableX(genParams).run(); - typeTypes.put("-" + specialNativeType, - ClassName.get("it.cavallium.data.generator.nativedata", "Nullable" + specialNativeType) - ); - typeFamily.put("-" + specialNativeType, Family.NULLABLE_OTHER); - typeTypes.put("§" + specialNativeType, getImmutableArrayType(typeTypes, specialNativeType)); - typeFamily.put("§" + specialNativeType, Family.OTHER); - if (nextVersion.isPresent()) { - nextVersionTypeTypes.put("-" + specialNativeType, - ClassName.get("it.cavallium.data.generator.nativedata", "Nullable" + specialNativeType) - ); - nextVersionTypeFamily.put("-" + specialNativeType, Family.NULLABLE_OTHER); - nextVersionTypeTypes.put("§" + specialNativeType, getImmutableArrayType(typeTypes, specialNativeType)); - nextVersionTypeFamily.put("§" + specialNativeType, Family.OTHER); - } - typeOptionalSerializers.put("-" + specialNativeType, - ClassName.get("it.cavallium.data.generator.nativedata", - "Nullable" + specialNativeType + "Serializer" - ) - ); - typeOptionalSerializers.put("§" + specialNativeType, - ClassName.get("it.cavallium.data.generator.nativedata", - "Array" + specialNativeType + "Serializer" - ) - ); - typeSerializeStatement.put("-" + specialNativeType, - new SerializeCodeBlockGenerator(CodeBlock - .builder() - .add("$T.Nullable" + specialNativeType + "SerializerInstance.serialize(dataOutput, ", - ClassName.get(joinPackage(versionPackage, ""), "Version") - ) - .build(), CodeBlock.builder().add(")").build()) - ); - typeSerializeStatement.put("§" + specialNativeType, - new SerializeCodeBlockGenerator(CodeBlock - .builder() - .add("$T.Array" + specialNativeType + "SerializerInstance.serialize(dataOutput, ", - ClassName.get(joinPackage(versionPackage, ""), "Version") - ) - .build(), CodeBlock.builder().add(")").build()) - ); - typeDeserializeStatement.put("-" + specialNativeType, - CodeBlock - .builder() - .add("$T.Nullable" + specialNativeType + "SerializerInstance.deserialize(dataInput)", - ClassName.get(joinPackage(versionPackage, ""), "Version") - ) - .build() - ); - typeDeserializeStatement.put("§" + specialNativeType, - CodeBlock - .builder() - .add("$T.Array" + specialNativeType + "SerializerInstance.deserialize(dataInput)", - ClassName.get(joinPackage(versionPackage, ""), "Version") - ) - .build() - ); - typeMustGenerateSerializer.put("-" + specialNativeType, false); - typeMustGenerateSerializer.put("§" + specialNativeType, false); + new GenINullableIType(genParams).run(); - } + new GenINullableBaseType(genParams).run(); - // Setup only the base types upgraders variables - for (String s : versionConfiguration.getClassMap().keySet()) { - if (nextVersion.isPresent()) { - typeOptionalUpgraders.put(s, ClassName.get(joinPackage(versionPackage, "upgraders"), s + "Upgrader")); - } - } + new GenINullableSuperType(genParams).run(); - // Generate the basic and super types - Stream - .concat(versionConfiguration.getClassMap().keySet().stream(), dataModel.getSuperTypes().keySet().stream()) - .forEach((type) -> { - boolean isBasic = versionConfiguration.getClassMap().containsKey(type); - typeOptionalSerializers.put(type, - ClassName.get(joinPackage(versionPackage, "serializers"), type + "Serializer") - ); - typeSerializeStatement.put(type, - new SerializeCodeBlockGenerator(CodeBlock - .builder() - .add("$T." + type + "SerializerInstance.serialize(dataOutput, ", versionClassType) - .build(), CodeBlock.builder().add(")").build()) - ); - typeDeserializeStatement.put(type, - CodeBlock - .builder() - .add("$T." + type + "SerializerInstance.deserialize(dataInput)", versionClassType) - .build() - ); - typeMustGenerateSerializer.put(type, true); - typeTypes.put(type, ClassName.get(joinPackage(versionPackage, "data"), type)); - typeFamily.put(type, !isBasic ? Family.GENERIC : Family.BASIC); - if (nextVersion.isPresent()) { - nextVersionTypeTypes.put(type, ClassName.get(joinPackage(nextVersionPackage.get(), "data"), type)); - nextVersionTypeFamily.put(type, !isBasic ? Family.GENERIC : Family.BASIC); - } + new GenDataBaseX(genParams).run(); - NeededTypes neededTypes = registerNeededTypes(versionConfiguration, - !isBasic ? Family.GENERIC : Family.BASIC, - type, - nextVersion, - nextVersionPackage, - versionClassType, - versionPackage, - typeOptionalSerializers, - typeSerializeStatement, - typeDeserializeStatement, - typeMustGenerateSerializer, - typeTypes, - typeFamily, - nextVersionTypeTypes, - nextVersionTypeFamily, - () -> ClassName.get(joinPackage(versionPackage, "data"), type), - () -> ClassName.get(joinPackage(nextVersionPackage.orElseThrow(), "data"), type) - ); - }); + new GenDataSuperX(genParams).run(); - // Generate the special types - for (Entry entry : dataModel.getCustomTypes().entrySet()) { - String key = entry.getKey(); - CustomTypesConfiguration customTypeConfiguration = entry.getValue(); - Optional nextVersionCustomTypeConfiguration = nextVersion - .map(s -> Objects.requireNonNull(dataModel.getCustomTypes().get(key), - () -> "Custom type " + key + " not found in version " + s - )); - typeOptionalSerializers.put(key, ClassName.bestGuess(customTypeConfiguration.serializer)); - typeSerializeStatement.put(key, - new SerializeCodeBlockGenerator(CodeBlock - .builder() - .add("$T." + key + "SerializerInstance.serialize(dataOutput, ", versionClassType) - .build(), CodeBlock.builder().add(")").build()) - ); - typeDeserializeStatement.put(key, - CodeBlock - .builder() - .add("$T." + key + "SerializerInstance.deserialize(dataInput)", versionClassType) - .build() - ); - typeMustGenerateSerializer.put(key, false); - typeTypes.put(key, customTypeConfiguration.getJavaClassType()); - typeFamily.put(key, Family.OTHER); - if (nextVersionCustomTypeConfiguration.isPresent()) { - nextVersionTypeTypes.put(key, nextVersionCustomTypeConfiguration.get().getJavaClassType()); - nextVersionTypeFamily.put(key, Family.OTHER); - } - - var arrayClassName = customTypeConfiguration.getJavaClassType(); - var neededTypes = registerNeededTypes(versionConfiguration, - Family.OTHER, - key, - nextVersion, - nextVersionPackage, - versionClassType, - versionPackage, - typeOptionalSerializers, - typeSerializeStatement, - typeDeserializeStatement, - typeMustGenerateSerializer, - typeTypes, - typeFamily, - nextVersionTypeTypes, - nextVersionTypeFamily, - () -> arrayClassName, - () -> arrayClassName - ); - } - - for (String type : typeTypes.keySet()) { - var a1 = typeSerializeStatement.get(type).toString().replace("\n", ""); - var a2 = typeDeserializeStatement.get(type).toString().replace("\n", ""); - var a3 = typeTypes.get(type).toString().replace("\n", ""); - if (logger.isDebugEnabled()) { - logger.debug("Found type: {\n\ttype: \"" + type + "\",\n\tclass: \"" + a3 + "\",\n\tserialize: \"" + a1 - + "\",\n\tdeserialize: \"" + a2 + "\"\n}"); - } else { - switch (type.charAt(0)) { - case '$' -> logger.debug("Found array: " + type.substring(1)); - case '-' -> logger.debug("Found nullable type: " + type.substring(1)); - default -> logger.debug("Found type: " + type); - } - } - } - } - - // Check if all types exist - { - for (Entry e : versionConfiguration.getClassMap().entrySet()) { - String type = e.getKey(); - ParsedClass typeConfig = e.getValue(); - for (Entry entry : typeConfig.data.entrySet()) { - String field = entry.getKey(); - String fieldType = entry.getValue(); - if (!typeTypes.containsKey(fieldType)) { - throw new UnsupportedOperationException( - "Unknown type '" + fieldType + "' of field '" + field + "' in class '" + type + "' in version '" - + versionConfiguration.getName() + "'"); - } - } - } - } - - // Generate the nullable types and serializers - { - for (String type : typeTypes.keySet()) { - if (type.startsWith("-")) { - if (typeMustGenerateSerializer.get(type)) { - String substring = type.substring(1); - var nullableClassType = ClassName.get(joinPackage(versionPackage, "data.nullables"), - "Nullable" + substring - ); - - // Create the nullable X serializer class - { - var nullableSerializerClass = TypeSpec.classBuilder("Nullable" + substring + "Serializer"); - nullableSerializerClass.addModifiers(Modifier.PUBLIC); - nullableSerializerClass.addModifiers(Modifier.FINAL); - nullableSerializerClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( - "it.cavallium.data.generator", - "DataSerializer" - ), nullableClassType)); - // Create the INSTANCE field - { - var thisSerializerClassType = ClassName.get(joinPackage(versionPackage, "serializers"), - "Nullable" + substring + "Serializer" - ); - var instanceField = FieldSpec - .builder(thisSerializerClassType, "INSTANCE") - .initializer("new $T()", thisSerializerClassType); - instanceField.addModifiers(Modifier.PUBLIC); - instanceField.addModifiers(Modifier.STATIC); - instanceField.addModifiers(Modifier.FINAL); - nullableSerializerClass.addField(instanceField.build()); - } - // Create the serialize method - { - var serializeMethod = createEmptySerializeMethod(nullableClassType); - serializeMethod.beginControlFlow("if (data.isEmpty())"); - serializeMethod.addStatement("dataOutput.writeBoolean(false)"); - serializeMethod.nextControlFlow("else"); - serializeMethod.addStatement("dataOutput.writeBoolean(true)"); - serializeMethod.addStatement("var dataContent = data.get()"); - serializeMethod.addStatement(typeSerializeStatement.get(substring).generate("dataContent")); - serializeMethod.endControlFlow(); - nullableSerializerClass.addMethod(serializeMethod.build()); - } - // Create the deserialize method - { - var deserializeMethod = createEmptyDeserializeMethod(nullableClassType); - deserializeMethod.addStatement("var isPresent = dataInput.readBoolean()"); - deserializeMethod.beginControlFlow("if (!isPresent)"); - deserializeMethod.addStatement("return $T.empty()", nullableClassType); - deserializeMethod.nextControlFlow("else"); - deserializeMethod.addCode("$[return $T.of(", nullableClassType); - deserializeMethod.addCode(typeDeserializeStatement.get(substring)); - deserializeMethod.addCode(");\n$]"); - deserializeMethod.endControlFlow(); - nullableSerializerClass.addMethod(deserializeMethod.build()); - } - // Save the resulting class in the main package - try { - writeClass(generatedFilesToDelete, - outPath, joinPackage(versionPackage, "serializers"), nullableSerializerClass); - } catch (IOException e) { - throw new IOError(e); - } - } - - // Create the nullable X types classes - { - var typeType = typeTypes.get(substring); - var family = typeFamily.get(substring); - var nullableTypeType = typeTypes.get("-" + substring); - var nullableTypeClass = TypeSpec.recordBuilder("Nullable" + capitalize(substring)); - nullableTypeClass.addModifiers(Modifier.PUBLIC); - nullableTypeClass.addModifiers(Modifier.STATIC); - nullableTypeClass.addModifiers(Modifier.FINAL); - if (family == Family.BASIC) { - nullableTypeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data.nullables"), - "INullableBaseType" - )); - } - if (family == Family.GENERIC) { - nullableTypeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data.nullables"), - "INullableSuperType" - )); - } - nullableTypeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data.nullables"), - "INullableIType" - )); - nullableTypeClass.addSuperinterface(INullable.class); - nullableTypeClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( "it.cavallium.data.generator", "TypedNullable"), typeType)); - var nullInstance = FieldSpec.builder(nullableTypeType, "NULL", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL); - nullInstance.initializer("new $T(null)", nullableTypeType); - nullableTypeClass.addField(nullInstance.build()); - var valueField = ParameterSpec.builder(typeType, "value"); - nullableTypeClass.addRecordComponent(valueField.build()); - var ofMethod = MethodSpec.methodBuilder("of"); - ofMethod.addModifiers(Modifier.PUBLIC); - ofMethod.addModifiers(Modifier.STATIC); - ofMethod.addModifiers(Modifier.FINAL); - ofMethod.addException(NullPointerException.class); - ofMethod.returns(nullableClassType); - ofMethod.addParameter(ParameterSpec.builder(typeType, "value").build()); - ofMethod.beginControlFlow("if (value != null)"); - ofMethod.addStatement("return new $T(value)", nullableTypeType); - ofMethod.nextControlFlow("else"); - ofMethod.addStatement("throw new $T()", NullPointerException.class); - ofMethod.endControlFlow(); - nullableTypeClass.addMethod(ofMethod.build()); - var ofNullableMethod = MethodSpec.methodBuilder("ofNullable"); - ofNullableMethod.addModifiers(Modifier.PUBLIC); - ofNullableMethod.addModifiers(Modifier.STATIC); - ofNullableMethod.addModifiers(Modifier.FINAL); - ofNullableMethod.returns(nullableClassType); - ofNullableMethod.addParameter(ParameterSpec.builder(typeType, "value").build()); - ofNullableMethod.beginControlFlow("if (value != null)"); - ofNullableMethod.addStatement("return new $T(value)", nullableTypeType); - ofNullableMethod.nextControlFlow("else"); - ofNullableMethod.addStatement("return NULL"); - ofNullableMethod.endControlFlow(); - nullableTypeClass.addMethod(ofNullableMethod.build()); - var emptyMethod = MethodSpec.methodBuilder("empty"); - emptyMethod.addModifiers(Modifier.PUBLIC); - emptyMethod.addModifiers(Modifier.STATIC); - emptyMethod.addModifiers(Modifier.FINAL); - emptyMethod.returns(nullableClassType); - emptyMethod.addStatement("return NULL"); - nullableTypeClass.addMethod(emptyMethod.build()); - if (OVERRIDE_ALL_NULLABLE_METHODS) { - var isEmptyMethod = MethodSpec.methodBuilder("isEmpty"); - isEmptyMethod.addModifiers(Modifier.PUBLIC); - isEmptyMethod.addModifiers(Modifier.FINAL); - isEmptyMethod.addAnnotation(Override.class); - isEmptyMethod.returns(TypeName.BOOLEAN); - isEmptyMethod.addStatement("return value == null"); - nullableTypeClass.addMethod(isEmptyMethod.build()); - var isPresentMethod = MethodSpec.methodBuilder("isPresent"); - isPresentMethod.addModifiers(Modifier.PUBLIC); - isPresentMethod.addModifiers(Modifier.FINAL); - isPresentMethod.addAnnotation(Override.class); - isPresentMethod.returns(TypeName.BOOLEAN); - isPresentMethod.addStatement("return value != null"); - nullableTypeClass.addMethod(isPresentMethod.build()); - var getMethod = MethodSpec.methodBuilder("get"); - getMethod.addModifiers(Modifier.PUBLIC); - getMethod.addModifiers(Modifier.FINAL); - getMethod.addException(NullPointerException.class); - getMethod.addAnnotation(Override.class); - getMethod.addAnnotation(NotNull.class); - getMethod.returns(typeType); - getMethod.beginControlFlow("if (value == null)"); - getMethod.addStatement("throw new $T()", NullPointerException.class); - getMethod.nextControlFlow("else"); - getMethod.addStatement("return value"); - getMethod.endControlFlow(); - nullableTypeClass.addMethod(getMethod.build()); - var orElseMethod = MethodSpec.methodBuilder("orElse"); - orElseMethod.addParameter(ParameterSpec - .builder(typeType, "defaultValue") - .addAnnotation(NotNull.class) - .build()); - orElseMethod.addModifiers(Modifier.PUBLIC); - orElseMethod.addModifiers(Modifier.FINAL); - orElseMethod.addAnnotation(Override.class); - orElseMethod.addAnnotation(NotNull.class); - orElseMethod.returns(typeType); - orElseMethod.beginControlFlow("if (value == null)"); - orElseMethod.addStatement("return defaultValue"); - orElseMethod.nextControlFlow("else"); - orElseMethod.addStatement("return value"); - orElseMethod.endControlFlow(); - nullableTypeClass.addMethod(orElseMethod.build()); - var orMethodGeneric = MethodSpec.methodBuilder("or"); - orMethodGeneric.addParameter(ParameterSpec - .builder(ParameterizedTypeName.get( - ClassName.get("it.cavallium.data.generator", "NativeNullable"), - WildcardTypeName.subtypeOf(typeType)), "fallback") - .addAnnotation(NotNull.class) - .build()); - orMethodGeneric.addModifiers(Modifier.PUBLIC); - orMethodGeneric.addModifiers(Modifier.FINAL); - orMethodGeneric.addAnnotation(Override.class); - orMethodGeneric.addAnnotation(NotNull.class); - orMethodGeneric.returns(nullableClassType); - orMethodGeneric.beginControlFlow("if (value == null)"); - orMethodGeneric.beginControlFlow("if (fallback.getClass() == $T.class)", nullableClassType); - orMethodGeneric.addStatement("return ($T) fallback", nullableClassType); - orMethodGeneric.nextControlFlow("else"); - orMethodGeneric.addStatement("return ofNullable(fallback.getNullable())"); - orMethodGeneric.endControlFlow(); - orMethodGeneric.nextControlFlow("else"); - orMethodGeneric.addStatement("return this"); - orMethodGeneric.endControlFlow(); - nullableTypeClass.addMethod(orMethodGeneric.build()); - } - var orMethodSpecific = MethodSpec.methodBuilder("or"); - orMethodSpecific.addParameter(ParameterSpec - .builder(nullableTypeType, "fallback") - .addAnnotation(NotNull.class) - .build()); - orMethodSpecific.addModifiers(Modifier.PUBLIC); - orMethodSpecific.addModifiers(Modifier.FINAL); - orMethodSpecific.addAnnotation(NotNull.class); - orMethodSpecific.returns(nullableClassType); - orMethodSpecific.beginControlFlow("if (value == null)"); - orMethodSpecific.addStatement("return fallback"); - orMethodSpecific.nextControlFlow("else"); - orMethodSpecific.addStatement("return this"); - orMethodSpecific.endControlFlow(); - nullableTypeClass.addMethod(orMethodSpecific.build()); - var getNullableMethod = MethodSpec.methodBuilder("getNullable"); - getNullableMethod.addModifiers(Modifier.PUBLIC); - getNullableMethod.addModifiers(Modifier.FINAL); - getNullableMethod.addAnnotation(Override.class); - getNullableMethod.addAnnotation(Nullable.class); - getNullableMethod.returns(typeType); - getNullableMethod.addStatement("return value"); - nullableTypeClass.addMethod(getNullableMethod.build()); - if (OVERRIDE_ALL_NULLABLE_METHODS) { - var getNullableParam = MethodSpec.methodBuilder("getNullable"); - getNullableParam.addParameter(ParameterSpec - .builder(typeType, "defaultValue") - .addAnnotation(Nullable.class) - .build()); - getNullableParam.addModifiers(Modifier.PUBLIC); - getNullableParam.addModifiers(Modifier.FINAL); - getNullableParam.addAnnotation(Override.class); - getNullableParam.addAnnotation(Nullable.class); - getNullableParam.returns(typeType); - getNullableParam.beginControlFlow("if (value == null)"); - getNullableParam.addStatement("return defaultValue"); - getNullableParam.nextControlFlow("else"); - getNullableParam.addStatement("return value"); - getNullableParam.endControlFlow(); - nullableTypeClass.addMethod(getNullableParam.build()); - } - var getDollarNullableMethod = MethodSpec.methodBuilder("$getNullable"); - getDollarNullableMethod.addModifiers(Modifier.PUBLIC); - getDollarNullableMethod.addModifiers(Modifier.FINAL); - getDollarNullableMethod.addAnnotation(Nullable.class); - getDollarNullableMethod.addAnnotation(Override.class); - getDollarNullableMethod.returns(typeType); - getDollarNullableMethod.addStatement("return this.getNullable()"); - nullableTypeClass.addMethod(getDollarNullableMethod.build()); - if (family == Family.BASIC) { - var getBaseType = MethodSpec.methodBuilder("getBaseType$"); - getBaseType.addModifiers(Modifier.PUBLIC); - getBaseType.addModifiers(Modifier.FINAL); - getBaseType.addException(NullPointerException.class); - getBaseType.addAnnotation(NotNull.class); - getBaseType.returns(ClassName.get(joinPackage(basePackageName, ""), "BaseType")); - getBaseType.addStatement("return $T." + capitalize(substring), ClassName.get(joinPackage(basePackageName, ""), "BaseType")); - nullableTypeClass.addMethod(getBaseType.build()); - } - if (family == Family.GENERIC) { - var getBaseType = MethodSpec.methodBuilder("getSuperType$"); - getBaseType.addModifiers(Modifier.PUBLIC); - getBaseType.addModifiers(Modifier.FINAL); - getBaseType.addException(NullPointerException.class); - getBaseType.addAnnotation(NotNull.class); - getBaseType.returns(ClassName.get(joinPackage(basePackageName, ""), "SuperType")); - getBaseType.addStatement("return $T." + capitalize(substring), ClassName.get(joinPackage(basePackageName, ""), "SuperType")); - nullableTypeClass.addMethod(getBaseType.build()); - } - - try { - writeClass(generatedFilesToDelete, - outPath, joinPackage(versionPackage, "data.nullables"), nullableTypeClass); - } catch (IOException e) { - throw new IOError(e); - } - } - } - } - } - } - - // Generate the array types and serializers - { - for (String type : typeTypes.keySet()) { - if (type.startsWith("§")) { - if (typeMustGenerateSerializer.get(type)) { - String substring = type.substring(1); - var classType = ClassName.get(joinPackage(versionPackage, "data"), substring); - var arrayClassType = getImmutableArrayType(classType); - - // Create the array X serializer class - { - var arraySerializerClass = TypeSpec.classBuilder("Array" + substring + "Serializer"); - arraySerializerClass.addModifiers(Modifier.PUBLIC); - arraySerializerClass.addModifiers(Modifier.FINAL); - arraySerializerClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( - "it.cavallium.data.generator", - "DataSerializer" - ), arrayClassType)); - // Create the INSTANCE field - { - var thisSerializerClassType = ClassName.get(joinPackage(versionPackage, "serializers"), - "Array" + substring + "Serializer" - ); - var instanceField = FieldSpec - .builder(thisSerializerClassType, "INSTANCE") - .initializer("new $T()", thisSerializerClassType); - instanceField.addModifiers(Modifier.PUBLIC); - instanceField.addModifiers(Modifier.STATIC); - instanceField.addModifiers(Modifier.FINAL); - arraySerializerClass.addField(instanceField.build()); - } - // Create the serialize method - { - var serializeMethod = createEmptySerializeMethod(arrayClassType); - serializeMethod.addStatement("dataOutput.writeInt(data.size())"); - serializeMethod.beginControlFlow("for (int i = 0; i < data.size(); i++)"); - serializeMethod.addStatement(typeSerializeStatement.get(substring).generate("data.get(i)")); - serializeMethod.endControlFlow(); - arraySerializerClass.addMethod(serializeMethod.build()); - } - // Create the deserialize method - { - var deserializeMethod = createEmptyDeserializeMethod(arrayClassType); - deserializeMethod.addStatement("int length = dataInput.readInt()"); - deserializeMethod.addStatement("var data = new $T[length]", classType); - deserializeMethod.beginControlFlow("for (int i = 0; i < length; i++)"); - deserializeMethod.addStatement(CodeBlock.join(List.of(CodeBlock.of("data[i] = "), - typeDeserializeStatement.get(substring) - ), "")); - deserializeMethod.endControlFlow(); - deserializeMethod.addStatement("return $T.of(data)", - (arrayClassType instanceof ParameterizedTypeName - ? ((ParameterizedTypeName) arrayClassType).rawType : arrayClassType) - ); - arraySerializerClass.addMethod(deserializeMethod.build()); - } - // Save the resulting class in the main package - try { - writeClass(generatedFilesToDelete, - outPath, joinPackage(versionPackage, "serializers"), arraySerializerClass); - } catch (IOException e) { - throw new IOError(e); - } - } - } - } - } - } - - // Generate the base types serializers - { - for (Entry classConfigurationEntry : versionConfiguration.getClassMap().entrySet()) { - String type = classConfigurationEntry.getKey(); - ParsedClass baseTypeConfiguration = classConfigurationEntry.getValue(); - var classType = ClassName.get(joinPackage(versionPackage, "data"), type); - - // Create the basic X serializer class - { - var serializerClass = TypeSpec.classBuilder(type + "Serializer"); - serializerClass.addModifiers(Modifier.PUBLIC); - serializerClass.addModifiers(Modifier.FINAL); - serializerClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( - "it.cavallium.data.generator", - "DataSerializer" - ), classType)); - // Create the INSTANCE field - { - var thisSerializerClassType = ClassName.get(joinPackage(versionPackage, "serializers"), - type + "Serializer" - ); - var instanceField = FieldSpec - .builder(thisSerializerClassType, "INSTANCE") - .initializer("new $T()", thisSerializerClassType); - instanceField.addModifiers(Modifier.PUBLIC); - instanceField.addModifiers(Modifier.STATIC); - instanceField.addModifiers(Modifier.FINAL); - serializerClass.addField(instanceField.build()); - } - // Create the serialize method - { - var serializeMethod = createEmptySerializeMethod(classType); - if (baseTypeConfiguration.data != null) { - for (Entry entry : baseTypeConfiguration.data.entrySet()) { - String field = entry.getKey(); - String fieldType = entry.getValue(); - serializeMethod.addStatement(typeSerializeStatement - .get(fieldType) - .generate("data." + field + "()")); - } - } - serializerClass.addMethod(serializeMethod.build()); - } - // Create the deserialize method - { - var deserializeMethod = createEmptyDeserializeMethod(classType); - deserializeMethod.addCode("$[return $T.of(\n$]", classType); - deserializeMethod.addCode("$>"); - AtomicInteger i = new AtomicInteger(baseTypeConfiguration.data.size()); - for (Entry entry : baseTypeConfiguration.data.entrySet()) { - String field = entry.getKey(); - String fieldType = entry.getValue(); - boolean isLast = i.decrementAndGet() == 0; - deserializeMethod.addCode(typeDeserializeStatement.get(fieldType)).addCode((isLast ? "" : ",") + "\n"); - } - deserializeMethod.addCode("$<"); - deserializeMethod.addStatement(")"); - serializerClass.addMethod(deserializeMethod.build()); - } - // Save the resulting class in the main package - try { - writeClass(generatedFilesToDelete, outPath, joinPackage(versionPackage, "serializers"), serializerClass); - } catch (IOException e) { - throw new IOError(e); - } - } - - // Create the basic X upgrader class - { - if (nextVersion.isPresent()) { - var nextVersionClassType = ClassName.get(joinPackage(nextVersionPackage.get(), "data"), type); - - var upgraderClass = TypeSpec.classBuilder(type + "Upgrader"); - upgraderClass.addModifiers(Modifier.PUBLIC); - upgraderClass.addModifiers(Modifier.FINAL); - upgraderClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( - "it.cavallium.data.generator", - "DataUpgrader" - ), classType, nextVersionClassType)); - // Create the INSTANCE field - { - var thisUpgraderClassType = ClassName.get(joinPackage(versionPackage, "upgraders"), type + "Upgrader"); - var instanceField = FieldSpec - .builder(thisUpgraderClassType, "INSTANCE") - .initializer("new $T()", thisUpgraderClassType); - instanceField.addModifiers(Modifier.PUBLIC); - instanceField.addModifiers(Modifier.STATIC); - instanceField.addModifiers(Modifier.FINAL); - upgraderClass.addField(instanceField.build()); - } - // Create the upgrade method - { - var deserializeMethod = MethodSpec.methodBuilder("upgrade"); - deserializeMethod.addAnnotation(NotNull.class); - deserializeMethod.addAnnotation(Override.class); - deserializeMethod.addModifiers(Modifier.PUBLIC); - deserializeMethod.addModifiers(Modifier.FINAL); - deserializeMethod.returns(nextVersionClassType); - deserializeMethod.addParameter(ParameterSpec - .builder(classType, "data") - .addAnnotation(NotNull.class) - .build()); - deserializeMethod.addException(IOException.class); - Object2IntLinkedOpenHashMap currentVarNumber = new Object2IntLinkedOpenHashMap<>( - baseTypeConfiguration.getData().size()); - Object2ObjectLinkedOpenHashMap currentVarTypeName = new Object2ObjectLinkedOpenHashMap<>( - baseTypeConfiguration.getData().size()); - Object2ObjectLinkedOpenHashMap currentVarTypeClass = new Object2ObjectLinkedOpenHashMap<>( - baseTypeConfiguration.getData().size()); - Object2ObjectLinkedOpenHashMap currentVarFamily = new Object2ObjectLinkedOpenHashMap<>( - baseTypeConfiguration.getData().size()); - ObjectOpenHashSet currentVarUpgraded = new ObjectOpenHashSet<>( - baseTypeConfiguration.getData().size()); - ObjectOpenHashSet currentVarDeleted = new ObjectOpenHashSet<>(); - currentVarNumber.defaultReturnValue(-1); - deserializeMethod.addStatement("$T.requireNonNull(data)", Objects.class); - for (Entry stringStringEntry : baseTypeConfiguration.getData().entrySet()) { - String k = stringStringEntry.getKey(); - String value = stringStringEntry.getValue(); - currentVarNumber.addTo(k, 1); - currentVarTypeName.put(k, value); - currentVarTypeClass.put(k, typeTypes.get(value)); - currentVarFamily.put(k, typeFamily.get(value)); - currentVarUpgraded.remove(k); - deserializeMethod.addStatement("var $$field$$" + 0 + "$$" + k + " = data." + k + "()"); - } - - List list = new ArrayList<>(); - for (VersionTransformation versionTransformation : nextVersion.get().transformations) { - if (versionTransformation.isForClass(type)) { - list.add(versionTransformation); - } - } - var transformations = Collections.unmodifiableList(list); - AtomicInteger transformationNumber = new AtomicInteger(0); - HashMap currentTransformedFieldTypes = new HashMap<>(); - for (Entry stringClassConfigurationEntry : versionConfiguration.getClassMap().entrySet()) { - String className = stringClassConfigurationEntry.getKey(); - ParsedClass classConfiguration = stringClassConfigurationEntry.getValue(); - for (Entry entry : classConfiguration.getData().entrySet()) { - String fieldName = entry.getKey(); - String fieldType = entry.getValue(); - currentTransformedFieldTypes.put(className + "." + fieldName, typeTypes.get(fieldType)); - } - } - HashMap dataUpgraderInstanceFieldName = new HashMap<>(); - AtomicInteger nextDataUpgraderInstanceFieldId = new AtomicInteger(0); - HashMap dataInitializerInstanceFieldName = new HashMap<>(); - AtomicInteger nextDataInitializerInstanceFieldId = new AtomicInteger(0); - for (VersionTransformation transformationConfig : transformations) { - var transformation = transformationConfig.getTransformation(); - deserializeMethod.addCode("\n"); - deserializeMethod.addComment("TRANSFORMATION #" + transformationNumber.incrementAndGet() + ": " - + transformation.getTransformName()); - switch (transformation.getTransformName()) { - case "remove-data" -> { - var removeDataTransformation = (RemoveDataConfiguration) transformation; - { - deserializeMethod.addComment( - "Deleted $$field$$" + currentVarNumber.getInt(removeDataTransformation.from) + "$$" - + removeDataTransformation.from); - currentVarNumber.addTo(removeDataTransformation.from, 1); - currentVarTypeName.remove(removeDataTransformation.from); - currentVarTypeClass.remove(removeDataTransformation.from); - currentVarFamily.remove(removeDataTransformation.from); - currentVarUpgraded.remove(removeDataTransformation.from); - currentVarDeleted.add(removeDataTransformation.from); - } - Objects.requireNonNull(currentTransformedFieldTypes.remove( - removeDataTransformation.transformClass + "." + removeDataTransformation.from)); - } - case "move-data" -> { - var moveDataTransformation = (MoveDataConfiguration) transformation; - { - currentVarNumber.addTo(moveDataTransformation.to, 1); - currentVarTypeName.put(moveDataTransformation.to, - Objects.requireNonNull(currentVarTypeName.get(moveDataTransformation.from)) - ); - currentVarTypeClass.put(moveDataTransformation.to, - Objects.requireNonNull(currentVarTypeClass.get(moveDataTransformation.from)) - ); - currentVarFamily.put(moveDataTransformation.to, - Objects.requireNonNull(currentVarFamily.get(moveDataTransformation.from)) - ); - if (currentVarUpgraded.remove(moveDataTransformation.from)) { - currentVarUpgraded.add(moveDataTransformation.to); - } - currentVarDeleted.remove(moveDataTransformation.to); - deserializeMethod.addStatement( - "var $$field$$" + currentVarNumber.getInt(moveDataTransformation.to) + "$$" - + moveDataTransformation.to + " = $$field$$" + currentVarNumber.getInt( - moveDataTransformation.from) + "$$" + moveDataTransformation.from); - } - { - deserializeMethod.addComment( - "Deleted $$field$$" + currentVarNumber.getInt(moveDataTransformation.from) + "$$" - + moveDataTransformation.from); - currentVarNumber.addTo(moveDataTransformation.from, 1); - currentVarTypeName.remove(moveDataTransformation.from); - currentVarTypeClass.remove(moveDataTransformation.from); - currentVarFamily.remove(moveDataTransformation.from); - currentVarUpgraded.remove(moveDataTransformation.from); - currentVarDeleted.add(moveDataTransformation.from); - } - currentTransformedFieldTypes.put( - moveDataTransformation.transformClass + "." + moveDataTransformation.to, - Objects.requireNonNull(currentTransformedFieldTypes.remove( - moveDataTransformation.transformClass + "." + moveDataTransformation.from)) - ); - } - case "upgrade-data" -> { - var upgradeDataTransformation = (UpgradeDataConfiguration) transformation; - TypeName fromType = currentTransformedFieldTypes.get( - upgradeDataTransformation.transformClass + "." + upgradeDataTransformation.from); - TypeName fromTypeBoxed = fromType.isPrimitive() ? fromType.box() : fromType; - String toTypeName = nextVersion.get().getClassMap() - .get(upgradeDataTransformation.transformClass) - .getData() - .get(upgradeDataTransformation.from); - TypeName toType = nextVersionTypeTypes.get(toTypeName); - if (toType == null) { - throw new IllegalArgumentException( - "Type " + toTypeName + " does not exist in version " + nextVersion - .map(ComputedVersion::getName) - .orElse("---")); - } - TypeName toTypeBoxed = toType.isPrimitive() ? toType.box() : toType; - deserializeMethod.addStatement( - "$T $$field$$" + (currentVarNumber.getInt(upgradeDataTransformation.from) + 1) + "$$" - + upgradeDataTransformation.from, toType); - var dataUpgraderClass = ClassName.bestGuess(upgradeDataTransformation.upgrader); - var dataUpgraderFieldName = dataUpgraderInstanceFieldName.computeIfAbsent(dataUpgraderClass, - _unused -> { - var dataUpgraderType = ParameterizedTypeName.get(ClassName.get(DataUpgrader.class), - fromTypeBoxed, - toTypeBoxed - ); - var fieldName = "DATA_UPGRADER_" + nextDataUpgraderInstanceFieldId.getAndIncrement(); - var fieldSpec = FieldSpec.builder(dataUpgraderType, - fieldName, - Modifier.PRIVATE, - Modifier.STATIC, - Modifier.FINAL - ); - fieldSpec.initializer("($T) new $T()", dataUpgraderType, dataUpgraderClass); - upgraderClass.addField(fieldSpec.build()); - return fieldName; - } - ); - deserializeMethod.beginControlFlow(""); - deserializeMethod.addStatement( - "$T upgraded = ($T) " + dataUpgraderFieldName + ".upgrade(($T) $$field$$" - + currentVarNumber.getInt(upgradeDataTransformation.from) + "$$" - + upgradeDataTransformation.from + ")", toType, toTypeBoxed, fromTypeBoxed); - deserializeMethod.addStatement( - "$$field$$" + (currentVarNumber.getInt(upgradeDataTransformation.from) + 1) + "$$" - + upgradeDataTransformation.from + " = upgraded"); - deserializeMethod.endControlFlow(); - Objects.requireNonNull(currentTransformedFieldTypes.remove( - upgradeDataTransformation.transformClass + "." + upgradeDataTransformation.from)); - currentTransformedFieldTypes.put( - upgradeDataTransformation.transformClass + "." + upgradeDataTransformation.from, toType); - currentVarNumber.addTo(upgradeDataTransformation.from, 1); - currentVarTypeName.put(upgradeDataTransformation.from, toTypeName); - currentVarTypeClass.put(upgradeDataTransformation.from, toType); - currentVarFamily.put(upgradeDataTransformation.from, - Objects.requireNonNull(typeFamily.get(toTypeName), - () -> "Type \"" + toTypeName + "\" has no type family!" - ) - ); - currentVarUpgraded.add(upgradeDataTransformation.from); - currentVarDeleted.remove(upgradeDataTransformation.from); - } - case "new-data" -> { - var newDataTransformation = (NewDataConfiguration) transformation; - String newTypeName = nextVersion.get().getClassMap() - .get(newDataTransformation.transformClass) - .getData() - .get(newDataTransformation.to); - TypeName newType = nextVersionTypeTypes.get(newTypeName); - Objects.requireNonNull(newType, - () -> "Type \"" + newTypeName + "\" is not present from next version " + versionConfiguration.getName() - + " to version " + nextVersion.get() + " in upgrader " - + newDataTransformation.transformClass + "." + newDataTransformation.to - ); - TypeName newTypeBoxed = newType.isPrimitive() ? newType.box() : newType; - { - currentVarNumber.addTo(newDataTransformation.to, 1); - currentVarTypeName.put(newDataTransformation.to, newTypeName); - currentVarTypeClass.put(newDataTransformation.to, newType); - currentVarFamily.put(newDataTransformation.to, - Objects.requireNonNull(typeFamily.get(newTypeName), - () -> "Type \"" + newTypeName + "\" has no type family!" - ) - ); - currentVarUpgraded.add(newDataTransformation.to); - currentVarDeleted.remove(newDataTransformation.to); - - var dataInitializerClass = ClassName.bestGuess(newDataTransformation.initializer); - var dataInitializerFieldName = dataInitializerInstanceFieldName.computeIfAbsent( - dataInitializerClass, - _unused -> { - var dataInitializerType = ParameterizedTypeName.get(ClassName.get(DataInitializer.class), - newTypeBoxed - ); - var fieldName = - "DATA_INITIALIZER_" + nextDataInitializerInstanceFieldId.getAndIncrement(); - var fieldSpec = FieldSpec.builder(dataInitializerType, - fieldName, - Modifier.PRIVATE, - Modifier.STATIC, - Modifier.FINAL - ); - fieldSpec.initializer("($T) new $T()", dataInitializerType, dataInitializerClass); - upgraderClass.addField(fieldSpec.build()); - return fieldName; - } - ); - - deserializeMethod.addStatement( - "var $$field$$" + currentVarNumber.getInt(newDataTransformation.to) + "$$" - + newDataTransformation.to + " = " + dataInitializerFieldName + ".initialize()"); - } - if (currentTransformedFieldTypes.put( - newDataTransformation.transformClass + "." + newDataTransformation.to, newType) != null) { - throw new IllegalStateException(); - } - } - default -> throw new UnsupportedOperationException( - "Unknown transform type: " + transformation.getTransformName()); - } - } - deserializeMethod.addCode("\n"); - deserializeMethod.addComment( - "Upgrade the remaining untouched values to the new version before returning"); - - var nextVersionFieldTypes = nextVersion.get().getClassMap().get(type).getData(); - for (var e : currentVarNumber.object2IntEntrySet()) { - String key = e.getKey(); - int number = e.getIntValue(); - if (!currentVarDeleted.contains(key) && !currentVarUpgraded.contains(key)) { - Family currentFamily = Objects.requireNonNull(currentVarFamily.get(key)); - switch (currentFamily) { - case OTHER: - case SPECIAL_NATIVE: - // Don't upgrade "other" families because they are already upgraded - continue; - } - - String toTypeName = nextVersionFieldTypes.get(key); - Family toFamily = typeFamily.get(toTypeName); - TypeName toType = nextVersionTypeTypes.get(toTypeName); - Objects.requireNonNull(toType, "Field " + key + " of type " - + type + " is missing in version " + nextVersion.orElse(null)); - TypeName toTypeBoxed = toType.isPrimitive() ? toType.box() : toType; - { - currentVarNumber.addTo(key, 1); - currentVarTypeName.put(key, toTypeName); - currentVarTypeClass.put(key, toType); - currentVarFamily.put(key, toFamily); - currentVarUpgraded.add(key); - currentVarDeleted.remove(key); - - switch (currentFamily) { - case BASIC, GENERIC -> deserializeMethod.addCode(buildStatementUpgradeBaseType(versionPackage, - nextVersionPackage.get(), - number, - key, - toTypeName, - toFamily, - toType, - toTypeBoxed - )); - case I_TYPE_ARRAY -> deserializeMethod.addCode(buildStatementUpgradeITypeArrayField( - versionPackage, - nextVersionPackage.get(), - number, - key, - toTypeName, - toFamily, - toType, - toTypeBoxed - )); - case NULLABLE_BASIC -> deserializeMethod.addCode(buildStatementUpgradeNullableBasicField( - versionPackage, - nextVersionPackage.get(), - number, - key, - toTypeName, - toFamily, - toType, - toTypeBoxed, - nextVersionTypeTypes.get(toTypeName.substring(1)) - )); - case NULLABLE_GENERIC -> deserializeMethod.addCode(buildStatementUpgradeNullableGenericField( - versionPackage, - nextVersionPackage.get(), - number, - key, - toTypeName, - toFamily, - toType, - toTypeBoxed, - nextVersionTypeTypes.get(toTypeName.substring(1)) - )); - case NULLABLE_OTHER -> deserializeMethod.addCode(buildStatementUpgradeNullableOtherField( - versionPackage, - nextVersionPackage.get(), - number, - key, - toTypeName, - toFamily, - toType, - toTypeBoxed - )); - default -> throw new IllegalStateException("Unexpected value: " + currentFamily); - } - } - } - } - - deserializeMethod.addCode("return $T.of(", nextVersionClassType); - AtomicBoolean isFirst = new AtomicBoolean(true); - for (Entry entry : nextVersionFieldTypes.entrySet()) { - String field = entry.getKey(); - String fieldType = entry.getValue(); - if (!isFirst.getAndSet(false)) { - deserializeMethod.addCode(", "); - } - if (currentVarNumber.getInt(field) < 0) { - throw new IllegalStateException( - "Field " + field + " in class " + type + " has an invalid var number (" - + currentVarNumber.getInt(field) + ") after upgrading from version " + versionConfiguration.getName() - + " to version " + nextVersion.map(ComputedVersion::getName).orElse("---")); - } - deserializeMethod.addCode("$$field$$" + currentVarNumber.getInt(field) + "$$" + field); - } - deserializeMethod.addStatement(")"); - upgraderClass.addMethod(deserializeMethod.build()); - } - // Save the resulting class in the main package - try { - writeClass(generatedFilesToDelete, outPath, joinPackage(versionPackage, "upgraders"), upgraderClass); - } catch (IOException e) { - throw new IOError(e); - } - } - } - - } - } - - // Generate the super types serializers - { - for (Entry> entry : dataModel.getSuperTypes().entrySet()) { - String type = entry.getKey(); - Set superTypeConfiguration = entry.getValue(); - var classType = ClassName.get(joinPackage(versionPackage, "data"), type); - - // Create the super X serializer class - { - var serializerClass = TypeSpec.classBuilder(type + "Serializer"); - serializerClass.addModifiers(Modifier.PUBLIC); - serializerClass.addModifiers(Modifier.FINAL); - serializerClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( - "it.cavallium.data.generator", - "DataSerializer" - ), classType)); - // Create the INSTANCE field - { - var thisSerializerClassType = ClassName.get(joinPackage(versionPackage, "serializers"), - type + "Serializer" - ); - var instanceField = FieldSpec - .builder(thisSerializerClassType, "INSTANCE") - .initializer("new $T()", thisSerializerClassType); - instanceField.addModifiers(Modifier.PUBLIC); - instanceField.addModifiers(Modifier.STATIC); - instanceField.addModifiers(Modifier.FINAL); - serializerClass.addField(instanceField.build()); - } - // Create the checkIdValidity method - { - var checkIdValidityMethod = MethodSpec.methodBuilder("checkIdValidity"); - checkIdValidityMethod.addModifiers(Modifier.PUBLIC); - checkIdValidityMethod.addModifiers(Modifier.STATIC); - checkIdValidityMethod.addModifiers(Modifier.FINAL); - checkIdValidityMethod.returns(TypeName.VOID); - checkIdValidityMethod.addParameter(ParameterSpec.builder(TypeName.INT, "id").build()); - checkIdValidityMethod.addException(IOException.class); - checkIdValidityMethod.addStatement("if (id < 0) throw new $T(new $T(id))", - IOException.class, - IndexOutOfBoundsException.class - ); - checkIdValidityMethod.addStatement( - "if (id >= " + superTypeConfiguration.size() + ") throw new $T(new $T(id))", - IOException.class, - IndexOutOfBoundsException.class - ); - serializerClass.addMethod(checkIdValidityMethod.build()); - } - // Create the serialize method - { - var serializeMethod = createEmptySerializeMethod(classType); - serializeMethod.addStatement("int id = data.getMetaId$$" + type + "()"); - serializeMethod.addCode("\n"); - serializeMethod.addStatement("checkIdValidity(id)"); - serializeMethod.addCode("\n"); - serializeMethod.addStatement("dataOutput.writeByte(id)"); - serializeMethod.addCode("\n"); - serializeMethod.beginControlFlow("switch (id)"); - int i = 0; - for (String subType : superTypeConfiguration) { - serializeMethod.beginControlFlow("case " + i + ":"); - serializeMethod.addStatement("$T." + subType + "SerializerInstance.serialize(dataOutput, ($T) data)", - versionClassType, - ClassName.get(joinPackage(versionPackage, "data"), subType) - ); - serializeMethod.addStatement("break"); - serializeMethod.endControlFlow(); - i++; - } - serializeMethod.addStatement("default: throw new $T(new $T())", - IOException.class, - IndexOutOfBoundsException.class - ); - serializeMethod.endControlFlow(); - serializerClass.addMethod(serializeMethod.build()); - } - // Create the deserialize method - { - var deserializeMethod = createEmptyDeserializeMethod(classType); - deserializeMethod.addStatement("int id = dataInput.readUnsignedByte()"); - deserializeMethod.addCode("\n"); - deserializeMethod.addStatement("checkIdValidity(id)"); - deserializeMethod.addCode("\n"); - deserializeMethod.beginControlFlow("switch (id)"); - int i = 0; - for (String subType : superTypeConfiguration) { - deserializeMethod.addStatement("case " + i + ": return " + typeDeserializeStatement.get(subType)); - i++; - } - deserializeMethod.addStatement("default: throw new $T(new $T())", - IOException.class, - IndexOutOfBoundsException.class - ); - deserializeMethod.endControlFlow(); - serializerClass.addMethod(deserializeMethod.build()); - } - // Save the resulting class in the main package - try { - writeClass(generatedFilesToDelete, outPath, joinPackage(versionPackage, "serializers"), serializerClass); - } catch (IOException e) { - throw new IOError(e); - } - } - } - } - - // Create the Version class - { - var versionClass = TypeSpec.classBuilder("Version"); - versionClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get(joinPackage(basePackageName, ""), "IVersion"), - ClassName.get(joinPackage(versionPackage, "data"), "IBaseType") - )); - versionClass.addModifiers(Modifier.PUBLIC); - versionClass.addModifiers(Modifier.FINAL); - // Add a static variable for the current version - { - var versionNumberXField = FieldSpec - .builder(TypeName.INT, "VERSION") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.STATIC) - .addModifiers(Modifier.FINAL) - .initializer("$T." + versionConfiguration.getVersionVarName(), - ClassName.get(joinPackage(basePackageName, ""), "Versions") - ) - .build(); - versionClass.addField(versionNumberXField); - } - // Add a static instance for the current version - { - var versionInstanceField = FieldSpec - .builder(versionClassType, "INSTANCE") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.STATIC) - .addModifiers(Modifier.FINAL) - .initializer("new $T()", versionClassType) - .build(); - versionClass.addField(versionInstanceField); - } - // Add upgrader instances static fields - { - for (Entry entry : typeOptionalUpgraders.entrySet()) { - String type = entry.getKey(); - TypeName upgraderType = entry.getValue(); - var typeName = type; - if (type.startsWith("§")) { - typeName = "Array" + type.substring(1); - } else if (type.startsWith("-")) { - typeName = "Nullable" + type.substring(1); - } else { - typeName = type; - } - var upgraderStaticField = FieldSpec.builder(upgraderType, typeName + "UpgraderInstance"); - upgraderStaticField.addModifiers(Modifier.PUBLIC); - upgraderStaticField.addModifiers(Modifier.STATIC); - upgraderStaticField.addModifiers(Modifier.FINAL); - upgraderStaticField.initializer("new $T()", upgraderType); - versionClass.addField(upgraderStaticField.build()); - } - } - // Add serializer instances static fields - { - for (Entry entry : typeOptionalSerializers.entrySet()) { - String type = entry.getKey(); - TypeName serializerType = entry.getValue(); - var typeName = type; - if (type.startsWith("§")) { - typeName = "Array" + type.substring(1); - } else if (type.startsWith("-")) { - typeName = "Nullable" + type.substring(1); - } else { - typeName = type; - } - var serializerStaticField = FieldSpec.builder(serializerType, typeName + "SerializerInstance"); - serializerStaticField.addModifiers(Modifier.PUBLIC); - serializerStaticField.addModifiers(Modifier.STATIC); - serializerStaticField.addModifiers(Modifier.FINAL); - serializerStaticField.initializer("new $T()", serializerType); - versionClass.addField(serializerStaticField.build()); - } - } - // Add upgradeToNextVersion method - { - if (nextVersion.isPresent()) { - var upgradeToNextVersionMethodBuilder = MethodSpec - .methodBuilder("upgradeToNextVersion") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.STATIC) - .addModifiers(Modifier.FINAL) - .returns(ClassName.get(joinPackage(nextVersionPackage.get(), "data"), "IBaseType")) - .addException(IOException.class) - .addParameter(ParameterSpec - .builder(ClassName.get(joinPackage(versionPackage, "data"), "IBaseType"), "oldData") - .build()) - .beginControlFlow("switch (oldData.getBaseType$$()) "); - for (Entry entry : versionConfiguration.getClassMap().entrySet()) { - String type = entry.getKey(); - ParsedClass typeConfiguration = entry.getValue(); - var data = typeConfiguration.data; - upgradeToNextVersionMethodBuilder.addStatement( - "case " + type + ": return $T." + type + "UpgraderInstance.upgrade(($T) oldData)", - versionClassType, - ClassName.get(joinPackage(versionPackage, "data"), type) - ); - } - var upgradeToNextVersionMethod = upgradeToNextVersionMethodBuilder - .beginControlFlow("default: ") - .addStatement("throw new $T(\"Unknown type: \" + oldData.getBaseType$$())", IOException.class) - .endControlFlow() - .endControlFlow() - .build(); - versionClass.addMethod(upgradeToNextVersionMethod); - } - } - // Add getClass method - { - var getClassMethodBuilder = MethodSpec - .methodBuilder("getClass") - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Override.class) - .returns(ParameterizedTypeName.get(ClassName.get(Class.class), - WildcardTypeName.subtypeOf(ClassName.get(joinPackage(versionPackage, "data"), "IBaseType")) - )) - .addParameter(ParameterSpec - .builder(ClassName.get(joinPackage(basePackageName, ""), "BaseType"), "type") - .build()) - .beginControlFlow("switch (type)"); - for (Entry entry : versionConfiguration.getClassMap().entrySet()) { - String type = entry.getKey(); - ParsedClass typeConfiguration = entry.getValue(); - var data = typeConfiguration.data; - - getClassMethodBuilder.addStatement("case " + type + ": return $T.class", - ClassName.get(joinPackage(versionPackage, "data"), type) - ); - } - getClassMethodBuilder - .beginControlFlow("default: ") - .addStatement("throw new $T(\"Unknown type: \" + type)", IllegalArgumentException.class) - .endControlFlow() - .endControlFlow(); - versionClass.addMethod(getClassMethodBuilder.build()); - } - // Add getSerializer method - { - var getSerializerMethodBuilder = MethodSpec - .methodBuilder("getSerializer") - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Override.class) - .addTypeVariable(TypeVariableName.get("T", - ClassName.get(joinPackage(versionPackage, "data"), "IBaseType") - )) - .returns(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), TypeVariableName.get("T"))) - .addException(IOException.class) - .addParameter(ParameterSpec - .builder(ClassName.get(joinPackage(basePackageName, ""), "BaseType"), "type") - .build()) - .beginControlFlow("switch (type)"); - for (Entry entry : versionConfiguration.getClassMap().entrySet()) { - String type = entry.getKey(); - ParsedClass typeConfiguration = entry.getValue(); - var data = typeConfiguration.data; - getSerializerMethodBuilder.addStatement("case " + type + ": return ($T) $T." + type + "SerializerInstance", - ParameterizedTypeName.get(ClassName.get(DataSerializer.class), TypeVariableName.get("T")), - versionClassType - ); - } - getSerializerMethodBuilder - .beginControlFlow("default: ") - .addStatement("throw new $T(\"Unknown type: \" + type)", IOException.class) - .endControlFlow() - .endControlFlow(); - versionClass.addMethod(getSerializerMethodBuilder.build()); - } - // Add createNullableOf method - { - var createNullableOfMethod = MethodSpec - .methodBuilder("createNullableOf") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL) - .returns(ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableIType")) - .addException(IOException.class) - .addParameter(ParameterSpec.builder(String.class, "type").build()) - .addParameter(ParameterSpec.builder(Object.class, "content").build()) - .beginControlFlow("switch (type)"); - for (String item : typeTypes.keySet()) { - if (item.startsWith("-")) { - String type = item.substring(1); - if (!specialNativeTypes.contains(type)) { - var nullableType = "Nullable" + type; - createNullableOfMethod.addStatement("case \"" + nullableType + "\": return $T.ofNullable(($T) content)", - typeTypes.get("-" + type), - typeTypes.get(type) - ); - } - } - } - createNullableOfMethod - .beginControlFlow("default: ") - .addStatement("throw new $T(\"Unknown nullable type: \" + type)", IOException.class) - .endControlFlow() - .endControlFlow(); - versionClass.addMethod(createNullableOfMethod.build()); - } - // Add createNullableOfBaseType method - { - var createNullableOfBaseTypeMethod = MethodSpec - .methodBuilder("createNullableOfBaseType") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL) - .returns(ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableBaseType")) - .addException(IOException.class) - .addParameter(ParameterSpec.builder(ClassName.get(joinPackage(basePackageName, ""), "BaseType"), "type").build()) - .addParameter(ParameterSpec.builder(Object.class, "content").build()) - .beginControlFlow("switch (type)"); - for (String item : typeTypes.keySet()) { - if (typeFamily.get(item) == Family.NULLABLE_BASIC) { - String type = item.substring(1); - if (!specialNativeTypes.contains(type)) { - createNullableOfBaseTypeMethod.addStatement("case " + type + ": return $T.ofNullable(($T) content)", - typeTypes.get("-" + type), - typeTypes.get(type) - ); - } - } - } - createNullableOfBaseTypeMethod - .beginControlFlow("default: ") - .addStatement("throw new $T(\"Unknown nullable type: \" + type)", IOException.class) - .endControlFlow() - .endControlFlow(); - versionClass.addMethod(createNullableOfBaseTypeMethod.build()); - } - // Add createNullableOfSuperType method - { - var createNullableOfSuperTypeMethod = MethodSpec - .methodBuilder("createNullableOfSuperType") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL) - .returns(ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableSuperType")) - .addException(IOException.class) - .addParameter(ParameterSpec.builder(ClassName.get(joinPackage(basePackageName, ""), "SuperType"), "type").build()) - .addParameter(ParameterSpec.builder(Object.class, "content").build()) - .beginControlFlow("switch (type)"); - for (String item : typeTypes.keySet()) { - if (typeFamily.get(item) == Family.NULLABLE_GENERIC) { - String type = item.substring(1); - if (!specialNativeTypes.contains(type)) { - createNullableOfSuperTypeMethod.addStatement("case " + type + ": return $T.ofNullable(($T) content)", - typeTypes.get("-" + type), - typeTypes.get(type) - ); - } - } - } - createNullableOfSuperTypeMethod - .beginControlFlow("default: ") - .addStatement("throw new $T(\"Unknown nullable type: \" + type)", IOException.class) - .endControlFlow() - .endControlFlow(); - versionClass.addMethod(createNullableOfSuperTypeMethod.build()); - } - // Add createArrayOf method - { - var createArrayOfMethod = MethodSpec - .methodBuilder("createArrayOf") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL) - .returns(Object.class) - .addException(IOException.class) - .addParameter(ParameterSpec.builder(String.class, "type").build()) - .addParameter(ParameterSpec.builder(TypeName.INT, "length").build()) - .beginControlFlow("switch (type)"); - for (String item : typeTypes.keySet()) { - if (item.startsWith("§")) { - String type = item.substring(1); - if (!specialNativeTypes.contains(type)) { - createArrayOfMethod.addStatement("case \"" + type + "\": return new $T[length]", typeTypes.get(type)); - } - } - } - createArrayOfMethod - .beginControlFlow("default: ") - .addStatement("throw new $T(\"Unknown nullable type: \" + type)", IOException.class) - .endControlFlow() - .endControlFlow(); - versionClass.addMethod(createArrayOfMethod.build()); - } - // Add getVersion method - { - var getVersionMethod = MethodSpec - .methodBuilder("getVersion") - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Override.class) - .returns(TypeName.INT) - .addStatement("return VERSION") - .build(); - versionClass.addMethod(getVersionMethod); - } - // Save the resulting class in the main package - try { - writeClass(generatedFilesToDelete, outPath, joinPackage(versionPackage, ""), versionClass); - } catch (IOException e) { - throw new IOError(e); - } - } - - // Create the interface IType - { - var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); - var iTypeInterface = TypeSpec.interfaceBuilder("IType"); - iTypeInterface.addModifiers(Modifier.PUBLIC); - iTypeInterface.addSuperinterface(ClassName.get(Serializable.class)); - try { - writeClass(generatedFilesToDelete, outPath, joinPackage(versionPackage, "data"), iTypeInterface); - } catch (IOException e) { - throw new IOError(e); - } - } - - // Create the interface IBaseType - { - var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); - var ibaseTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IBaseType"); - var ibaseTypeInterface = TypeSpec.interfaceBuilder("IBaseType"); - ibaseTypeInterface.addModifiers(Modifier.PUBLIC); - ibaseTypeInterface.addSuperinterface(iTypeInterfaceType); - // Create getBaseType$type method - { - var getBaseTypeMethod = MethodSpec - .methodBuilder("getBaseType$") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.ABSTRACT) - .returns(ClassName.get(joinPackage(basePackageName, ""), "BaseType")); - ibaseTypeInterface.addMethod(getBaseTypeMethod.build()); - } - try { - writeClass(generatedFilesToDelete, outPath, joinPackage(versionPackage, "data"), ibaseTypeInterface); - } catch (IOException e) { - throw new IOError(e); - } - } - - // Create the interface INullableIType - { - var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); - var inullableITypeInterface = TypeSpec.interfaceBuilder("INullableIType"); - inullableITypeInterface.addModifiers(Modifier.PUBLIC); - inullableITypeInterface.addSuperinterface(iTypeInterfaceType); - inullableITypeInterface.addSuperinterface(INullable.class); - try { - writeClass(generatedFilesToDelete, - outPath, joinPackage(versionPackage, "data.nullables"), inullableITypeInterface); - } catch (IOException e) { - throw new IOError(e); - } - } - - // Create the interface INullableBaseType - { - var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableIType"); - var inullableBaseTypeInterface = TypeSpec.interfaceBuilder("INullableBaseType"); - inullableBaseTypeInterface.addModifiers(Modifier.PUBLIC); - inullableBaseTypeInterface.addSuperinterface(iTypeInterfaceType); - var getBaseTypeMethod = MethodSpec - .methodBuilder("getBaseType$") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.ABSTRACT) - .returns(ClassName.get(joinPackage(basePackageName, ""), "BaseType")); - inullableBaseTypeInterface.addMethod(getBaseTypeMethod.build()); - try { - writeClass(generatedFilesToDelete, - outPath, joinPackage(versionPackage, "data.nullables"), inullableBaseTypeInterface); - } catch (IOException e) { - throw new IOError(e); - } - } - - // Create the interface INullableSuperType - { - var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableIType"); - var inullablesuperTypeInterface = TypeSpec.interfaceBuilder("INullableSuperType"); - inullablesuperTypeInterface.addModifiers(Modifier.PUBLIC); - inullablesuperTypeInterface.addSuperinterface(iTypeInterfaceType); - var getBaseTypeMethod = MethodSpec - .methodBuilder("getSuperType$") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.ABSTRACT) - .returns(ClassName.get(joinPackage(basePackageName, ""), "SuperType")); - inullablesuperTypeInterface.addMethod(getBaseTypeMethod.build()); - try { - writeClass(generatedFilesToDelete, - outPath, joinPackage(versionPackage, "data.nullables"), inullablesuperTypeInterface); - } catch (IOException e) { - throw new IOError(e); - } - } - - // Create the interfaces - { - for (Entry> superType : dataModel.getSuperTypes().entrySet()) { - String type = superType.getKey(); - Set superTypeConfiguration = superType.getValue(); - var iBaseTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IBaseType"); - var typeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), type); - var typeInterface = TypeSpec.interfaceBuilder(type); - typeInterface.addModifiers(Modifier.PUBLIC); - typeInterface.addSuperinterface(iBaseTypeInterfaceType); - var getMetaTypeMethod = MethodSpec - .methodBuilder("getMetaId$" + type) - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.ABSTRACT) - .returns(TypeName.INT); - typeInterface.addMethod(getMetaTypeMethod.build()); - var getBaseTypeMethod = MethodSpec - .methodBuilder("getBaseType$") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.ABSTRACT) - .returns(ClassName.get(joinPackage(basePackageName, ""), "BaseType")); - typeInterface.addMethod(getBaseTypeMethod.build()); - - // If it's the latest version, add the common methods - if (nextVersion.isEmpty()) { - var interfaceDataConfiguration = dataModel.getInterfaces().get(type); - if (interfaceDataConfiguration != null) { - // Extend this interface - for (String extendedInterface : interfaceDataConfiguration.extendInterfaces) { - typeInterface.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data"), extendedInterface)); - } - Map commonFields = new HashMap<>(); - for (Entry e : interfaceDataConfiguration.commonData.entrySet()) { - String key = e.getKey(); - String value = e.getValue(); - commonFields.put(key, new CommonField(key, value, true)); - } - for (Entry entry : interfaceDataConfiguration.commonGetters.entrySet()) { - String field = entry.getKey(); - String fieldType = entry.getValue(); - commonFields.put(field, new CommonField(field, fieldType, false)); - } - for (CommonField fieldInfo : commonFields.values()) { - var fieldTypeType = fieldInfo.fieldType.equals("?") ? ClassName.get("java.lang", "Object") - : typeTypes.get(fieldInfo.fieldType); - // Add common data getter - { - var getterMethod = MethodSpec - .methodBuilder(fieldInfo.fieldName) - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.ABSTRACT) - .returns(fieldTypeType); - if (!fieldTypeType.isPrimitive()) { - getterMethod.addAnnotation(NotNull.class); - } - typeInterface.addMethod(getterMethod.build()); - } - - // Add common data setter - if (fieldInfo.hasSetter) { - if (!fieldInfo.fieldType.equals("?")) { - var param = ParameterSpec.builder(fieldTypeType, fieldInfo.fieldName, Modifier.FINAL); - if (!fieldTypeType.isPrimitive()) { - param.addAnnotation(NotNull.class); - } - var setterMethod = MethodSpec - .methodBuilder("set" + capitalize(fieldInfo.fieldName)) - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.ABSTRACT) - .addParameter(param.build()) - .returns(typeInterfaceType) - .addAnnotation(NotNull.class); - typeInterface.addMethod(setterMethod.build()); - } - } - } - } - } - - try { - writeClass(generatedFilesToDelete, outPath, joinPackage(versionPackage, "data"), typeInterface); - } catch (IOException e) { - throw new IOError(e); - } - } - } - - // Create the base types classes - { - for (Entry stringClassConfigurationEntry : versionConfiguration.getClassMap().entrySet()) { - String type = stringClassConfigurationEntry.getKey(); - ParsedClass classConfiguration = stringClassConfigurationEntry.getValue(); - var typeClass = TypeSpec.recordBuilder(type); - typeClass.addModifiers(Modifier.PUBLIC); - typeClass.addModifiers(Modifier.STATIC); - typeClass.addModifiers(Modifier.FINAL); - typeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data"), "IBaseType")); - if (nextVersion.isEmpty() && useRecordBuilders) { - typeClass.addAnnotation(RecordBuilder.class); - } - var getBaseTypeMethod = MethodSpec - .methodBuilder("getBaseType$") - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL) - .addAnnotation(Override.class) - .returns(ClassName.get(joinPackage(basePackageName, ""), "BaseType")) - .addStatement("return $T." + type, ClassName.get(joinPackage(basePackageName, ""), "BaseType")); - typeClass.addMethod(getBaseTypeMethod.build()); - var superTypes = dataModel.getSuperTypes() - .entrySet() - .parallelStream() - .filter(((entry) -> entry.getValue().contains(type))) - .map((entry) -> Map.entry(entry.getKey(), indexOf(entry.getValue(), type))) - .collect(Collectors.toUnmodifiableSet()); - for (Entry superType : superTypes) { - typeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data"), superType.getKey())); - - var getMetaIdTypeField = FieldSpec - .builder(TypeName.INT, "META_ID$" + capitalizeAll(superType.getKey())) - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.STATIC) - .addModifiers(Modifier.FINAL) - .initializer("" + superType.getValue()); - typeClass.addField(getMetaIdTypeField.build()); - - var getMetaTypeMethod = MethodSpec - .methodBuilder("getMetaId$" + superType.getKey()) - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL) - .addAnnotation(Override.class) - .returns(TypeName.INT) - .addStatement("return this." + "META_ID$$" + capitalizeAll(superType.getKey())); - typeClass.addMethod(getMetaTypeMethod.build()); - } - for (Entry stringStringEntry : classConfiguration.getData().entrySet()) { - String key = stringStringEntry.getKey(); - String value = stringStringEntry.getValue(); - var isGetterOverride = false; - var isSetterOverride = false; - if (nextVersion.isEmpty()) { - for (Entry superType : superTypes) { - if (superType != null) { - var interfaceCommonDataConfiguration = dataModel.getInterfaces().getOrDefault(superType.getKey(), - null - ); - if (interfaceCommonDataConfiguration != null - && interfaceCommonDataConfiguration.commonData.containsKey(key) - && !interfaceCommonDataConfiguration.commonData.get(key).equals("?")) { - isGetterOverride = true; - isSetterOverride = true; - } - if (interfaceCommonDataConfiguration != null - && interfaceCommonDataConfiguration.commonGetters.containsKey(key) - && !interfaceCommonDataConfiguration.commonGetters.get(key).equals("?")) { - isGetterOverride = true; - } - } - } - } - - addField(typeClass, key, typeTypes.get(value), true, true, false); - addImmutableSetter(typeClass, - ClassName.get(joinPackage(versionPackage, "data"), type), - classConfiguration.getData().keySet(), - key, - typeTypes.get(value), - isSetterOverride - ); - } - // Add string representer only if this object is at the current version, the old data don't need a string representation... - if (nextVersion.isEmpty() && classConfiguration.getStringRepresenter() != null) { - typeClass.addMethod(MethodSpec - .methodBuilder("toString") - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Override.class) - .returns(String.class) - .addStatement("return " + classConfiguration.getStringRepresenter() + "(this)") - .build()); - } - - var ofConstructor = MethodSpec - .methodBuilder("of") - .returns(typeTypes.get(type)) - .addModifiers(Modifier.STATIC) - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL); - var mapConstructor = MethodSpec - .methodBuilder("parse") - .returns(typeTypes.get(type)) - .addModifiers(Modifier.STATIC) - .addModifiers(Modifier.PUBLIC) - .addModifiers(Modifier.FINAL); - mapConstructor.addException(IllegalStateException.class); - mapConstructor.addParameter(ParameterizedTypeName.get(Map.class, String.class, Object.class), "fields"); - mapConstructor.addStatement( - "if (fields.size() != " + classConfiguration.getData().size() + ") throw new $T()", - IllegalStateException.class - ); - var returnMapNewInstanceStamentBuilder = CodeBlock.builder().add("return new $T(", typeTypes.get(type)); - var returnOfNewInstanceStamentBuilder = CodeBlock.builder().add("return new $T(", typeTypes.get(type)); - boolean first = true; - for (Entry entry : classConfiguration.getData().entrySet()) { - if (!first) { - returnMapNewInstanceStamentBuilder.add(", "); - returnOfNewInstanceStamentBuilder.add(", "); - } else { - first = false; - } - String field = entry.getKey(); - String fieldType = entry.getValue(); - var fieldTypeType = typeTypes.get(fieldType); - mapConstructor.addStatement("if (!fields.containsKey(\"" + field + "\")) throw new $T()", - IllegalStateException.class - ); - boolean requiresNotNull = !fieldTypeType.isPrimitive(); - if (requiresNotNull) { - returnMapNewInstanceStamentBuilder.add("$T.requireNonNull(", Objects.class); - returnOfNewInstanceStamentBuilder.add("$T.requireNonNull(", Objects.class); - } - returnMapNewInstanceStamentBuilder.add("($T) fields.get(\"" + field + "\")", typeTypes.get(fieldType)); - returnOfNewInstanceStamentBuilder.add("($T) " + field + "", typeTypes.get(fieldType)); - var fieldTypeName = typeTypes.get(fieldType); - var parameterSpecBuilder = ParameterSpec.builder(fieldTypeName, "" + field); - if (!fieldTypeName.isPrimitive()) { - parameterSpecBuilder.addAnnotation(NotNull.class); - } - ofConstructor.addParameter(parameterSpecBuilder.build()); - if (requiresNotNull) { - returnMapNewInstanceStamentBuilder.add(")"); - returnOfNewInstanceStamentBuilder.add(")"); - } - } - if (first) { - typeClass.addField(FieldSpec - .builder(typeTypes.get(type), "INSTANCE", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) - .initializer("new $T()", typeTypes.get(type)) - .build()); - mapConstructor.addStatement("return INSTANCE"); - ofConstructor.addStatement("return INSTANCE"); - } else { - mapConstructor.addStatement(returnMapNewInstanceStamentBuilder.add(")").build()); - ofConstructor.addStatement(returnOfNewInstanceStamentBuilder.add(")").build()); - } - typeClass.addMethod(mapConstructor.build()); - typeClass.addMethod(ofConstructor.build()); - - try { - writeClass(generatedFilesToDelete, outPath, joinPackage(versionPackage, "data"), typeClass); - } catch (IOException e) { - throw new IOError(e); - } - } - } - - for (Path pathToDelete : generatedFilesToDelete) { - if (Files.exists(pathToDelete)) { - Files.delete(outPath.resolve(pathToDelete)); - logger.info("Deleting unused file: {}", pathToDelete); - } - } - } - } - - private void markFileAsCreated(Set generatedFilesToDelete, Path basePath, Path filePath) { - generatedFilesToDelete.remove(basePath.relativize(filePath)); + // Update the hash at the end + Files.writeString(hashPath, basePackageName + '\n' + useRecordBuilders + '\n' + curHash + '\n', + StandardCharsets.UTF_8, TRUNCATE_EXISTING, WRITE, CREATE); + generatedFilesToDelete.remove(outPath.relativize(hashPath)); } private TypeName getImmutableArrayType(HashMap typeTypes, String typeString) { @@ -2396,204 +218,6 @@ public class SourcesGenerator { } } - private CodeBlock buildStatementUpgradeBaseType( - String versionPackage, - String nextVersionPackage, - int number, - String key, - String toTypeName, - Family toFamily, - TypeName toType, - TypeName toTypeBoxed) { - var deserializeMethod = CodeBlock.builder(); - String inputFieldName = "$field$" + number + "$" + key; - String resultFieldName = "$field$" + (number + 1) + "$" + key; - deserializeMethod.addStatement("$T $N", toType, resultFieldName); - deserializeMethod.beginControlFlow(""); - deserializeMethod.addStatement("var value = $N", inputFieldName); - - var oldIBaseType = ClassName.get(joinPackage(versionPackage, "data"), "IBaseType"); - var upgradeBaseTypeField = MethodSpec.methodBuilder("upgradeBaseTypeField"); - upgradeBaseTypeField.addModifiers(Modifier.PRIVATE); - upgradeBaseTypeField.addModifiers(Modifier.STATIC); - upgradeBaseTypeField.addModifiers(Modifier.FINAL); - upgradeBaseTypeField.returns(Object.class); - upgradeBaseTypeField.addParameter(ParameterSpec - .builder(oldIBaseType, "value") - .addAnnotation(NotNull.class) - .build()); - upgradeBaseTypeField.addException(IOException.class); - - var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); - var oldIType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); - var oldINullableBaseType = ClassName.get(joinPackage(versionPackage, "data.nullables"), - "INullableBaseType" - ); - upgradeBaseTypeField.addStatement("$T.requireNonNull(value)", Objects.class); - deserializeMethod.addStatement("$N = ($T) $T.upgradeToNextVersion(($T) value)", - resultFieldName, - toTypeBoxed, - oldVersionType, - oldIBaseType - ); - - deserializeMethod.endControlFlow(); - return deserializeMethod.build(); - } - - private CodeBlock buildStatementUpgradeNullableOtherField( - String versionPackage, - String nextVersionPackage, - int number, - String key, - String toTypeName, - Family toFamily, - TypeName toType, - TypeName toTypeBoxed) { - var deserializeMethod = CodeBlock.builder(); - String inputFieldName = "$field$" + number + "$" + key; - String resultFieldName = "$field$" + (number + 1) + "$" + key; - deserializeMethod.addStatement("$T $N", toType, resultFieldName); - deserializeMethod.beginControlFlow(""); - deserializeMethod.addStatement("var value = $N", inputFieldName); - - - var oldINullableSuperType = ClassName.get(joinPackage(versionPackage, "data.nullables"), - "INullableSuperType" - ); - - var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); - var oldIBaseType = ClassName.get(joinPackage(versionPackage, "data"), "IBaseType"); - var oldIType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); - deserializeMethod.addStatement("$T.requireNonNull(value)", Objects.class); - deserializeMethod.addStatement("var content = value.$$getNullable()"); - deserializeMethod.addStatement("$N = $T.ofNullable(content)", resultFieldName, toTypeBoxed); - - deserializeMethod.endControlFlow(); - return deserializeMethod.build(); - } - - private CodeBlock buildStatementUpgradeNullableGenericField( - String versionPackage, - String nextVersionPackage, - int number, - String key, - String toTypeName, - Family toFamily, - TypeName toType, - TypeName toTypeBoxed, - TypeName toSuperType) { - var deserializeMethod = CodeBlock.builder(); - String inputFieldName = "$field$" + number + "$" + key; - String resultFieldName = "$field$" + (number + 1) + "$" + key; - deserializeMethod.addStatement("$T $N", toType, resultFieldName); - deserializeMethod.beginControlFlow(""); - deserializeMethod.addStatement("var value = $N", inputFieldName); - - - var oldINullableSuperType = ClassName.get(joinPackage(versionPackage, "data.nullables"), - "INullableSuperType" - ); - - var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); - var oldIBaseType = ClassName.get(joinPackage(versionPackage, "data"), "IBaseType"); - var oldIType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); - deserializeMethod.addStatement("$T.requireNonNull(value)", Objects.class); - deserializeMethod.addStatement("var content = value.$$getNullable()"); - deserializeMethod.addStatement("var newContent = content == null ? null : $T.requireNonNull(($T) $T.upgradeToNextVersion(($T) content))", - ClassName.get(Objects.class), - toSuperType, - oldVersionType, - oldIBaseType - ); - deserializeMethod.addStatement("$N = $T.ofNullable(newContent)", resultFieldName, toTypeBoxed); - - deserializeMethod.endControlFlow(); - return deserializeMethod.build(); - } - - private CodeBlock buildStatementUpgradeNullableBasicField( - String versionPackage, - String nextVersionPackage, - int number, - String key, - String toTypeName, - Family toFamily, - TypeName toType, - TypeName toTypeBoxed, - TypeName toBaseType) { - var deserializeMethod = CodeBlock.builder(); - String inputFieldName = "$field$" + number + "$" + key; - String resultFieldName = "$field$" + (number + 1) + "$" + key; - deserializeMethod.addStatement("$T $N", toType, resultFieldName); - deserializeMethod.beginControlFlow(""); - deserializeMethod.addStatement("var value = $N", inputFieldName); - - - var oldINullableBaseType = ClassName.get(joinPackage(versionPackage, "data.nullables"), - "INullableBaseType" - ); - - var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); - var oldIBaseType = ClassName.get(joinPackage(versionPackage, "data"), "IBaseType"); - var oldIType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); - deserializeMethod.addStatement("$T.requireNonNull(value)", Objects.class); - deserializeMethod.addStatement("var content = value.$$getNullable()"); - deserializeMethod.addStatement("var newContent = content == null ? null : $T.requireNonNull(($T) $T.upgradeToNextVersion(($T) content))", - ClassName.get(Objects.class), - toBaseType, - oldVersionType, - oldIBaseType - ); - deserializeMethod.addStatement("$N = $T.ofNullable(newContent)", resultFieldName, toTypeBoxed); - - - deserializeMethod.endControlFlow(); - return deserializeMethod.build(); - } - - private CodeBlock buildStatementUpgradeITypeArrayField( - String versionPackage, - @Nullable String nextVersionPackage, - int number, - String key, - String toTypeName, - Family toFamily, - TypeName toType, - TypeName toTypeBoxed) { - var deserializeMethod = CodeBlock.builder(); - String inputFieldName = "$field$" + number + "$" + key; - String resultFieldName = "$field$" + (number + 1) + "$" + key; - deserializeMethod.addStatement("$T $N", toType, resultFieldName); - deserializeMethod.beginControlFlow(""); - deserializeMethod.addStatement("var value = $N", inputFieldName); - - var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); - var oldIBaseType = ClassName.get(joinPackage(versionPackage, "data"), "IBaseType"); - var oldINullableBaseType = ClassName.get(joinPackage(versionPackage, "data.nullables"), - "INullableBaseType" - ); - deserializeMethod.addStatement("$T.requireNonNull(value)", Objects.class); - deserializeMethod.addStatement("var newArray = new $T[value.size()]", getArrayComponentType(toType)); - deserializeMethod.beginControlFlow("for (int i = 0; i < value.size(); i++)"); - deserializeMethod.addStatement("var item = value.get(i)", Array.class); - deserializeMethod.addStatement("var updatedItem = ($T) $T.upgradeToNextVersion(($T) item)", - getArrayComponentType(toType), - oldVersionType, - oldIBaseType - ); - deserializeMethod.addStatement("newArray[i] = updatedItem"); - deserializeMethod.endControlFlow(); - deserializeMethod.addStatement("$N = $T.of(newArray)", - resultFieldName, - toType instanceof ParameterizedTypeName ? ((ParameterizedTypeName) toType).rawType : toType - ); - - - deserializeMethod.endControlFlow(); - return deserializeMethod.build(); - } - private static String getSpecialNativePackage(String specialNativeType) { //noinspection SwitchStatementWithTooFewBranches return switch (specialNativeType) { @@ -2602,7 +226,8 @@ public class SourcesGenerator { }; } - private void registerArrayType(String versionPackage, + private void registerArrayType(ComputedVersion version, + String basePackageName, ClassName versionClassType, HashMap typeOptionalSerializers, HashMap typeSerializeStatement, @@ -2610,7 +235,7 @@ public class SourcesGenerator { HashMap typeMustGenerateSerializer, String type) { typeOptionalSerializers.put("§" + type, - ClassName.get(joinPackage(versionPackage, "serializers"), "Array" + type + "Serializer")); + ClassName.get(version .getSerializersPackage(basePackageName), "Array" + type + "Serializer")); typeSerializeStatement.put("§" + type, new SerializeCodeBlockGenerator( CodeBlock.builder().add("$T.Array" + type + "SerializerInstance.serialize(dataOutput, ", versionClassType) .build(), CodeBlock.builder().add(")").build())); @@ -2646,137 +271,6 @@ public class SourcesGenerator { return deserializeMethod; } - public record NeededTypes( - boolean nullableTypeNeeded, - boolean nextVersionNullableTypeNeeded, - boolean arrayTypeNeeded, - boolean nextVersionArrayTypeNeeded){} - - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - public NeededTypes registerNeededTypes(ComputedVersion versionConfiguration, - Family family, - String type, - Optional nextVersion, - Optional nextVersionPackage, - ClassName versionClassType, - String versionPackage, - HashMap typeOptionalSerializers, - HashMap typeSerializeStatement, - HashMap typeDeserializeStatement, - HashMap typeMustGenerateSerializer, - HashMap typeTypes, - HashMap typeFamily, - @Nullable HashMap nextVersionTypeTypes, - @Nullable HashMap nextVersionTypeFamily, - Supplier arrayClassName, - Supplier nextArrayClassName) { - // Check if the nullable type is needed - boolean nullableTypeNeeded = versionConfiguration.getClassMap() - .values() - .parallelStream() - .map(ParsedClass::getData) - .map(Map::values) - .flatMap(Collection::parallelStream) - .filter((typeZ) -> typeZ.startsWith("-")) - .map((typeZ) -> typeZ.substring(1)) - .anyMatch((typeZ) -> typeZ.equals(type)); - - boolean nextVersionNullableTypeNeeded = nextVersion - .filter(s -> s.getClassMap() - .values() - .parallelStream() - .map(ParsedClass::getData) - .map(Map::values) - .flatMap(Collection::parallelStream) - .filter((typeZ) -> typeZ.startsWith("-")) - .map((typeZ) -> typeZ.substring(1)) - .anyMatch((typeZ) -> typeZ.equals(type))) - .isPresent(); - - Family nullableFamily = switch (family) { - case BASIC -> Family.NULLABLE_BASIC; - case GENERIC -> Family.NULLABLE_GENERIC; - case OTHER -> Family.NULLABLE_OTHER; - default -> throw new IllegalStateException("Unexpected value: " + family); - }; - - if (nullableTypeNeeded) { - typeOptionalSerializers.put("-" + type, - ClassName.get(joinPackage(versionPackage, "serializers"), "Nullable" + type + "Serializer")); - typeSerializeStatement.put("-" + type, new SerializeCodeBlockGenerator(CodeBlock.builder() - .add("$T.Nullable" + type + "SerializerInstance.serialize(dataOutput, ", versionClassType).build(), - CodeBlock.builder().add(")").build())); - typeDeserializeStatement.put("-" + type, CodeBlock.builder() - .add("$T.Nullable" + type + "SerializerInstance.deserialize(dataInput)", versionClassType).build()); - typeMustGenerateSerializer.put("-" + type, true); - typeTypes.put("-" + type, ClassName.get(joinPackage(versionPackage, "data.nullables"), "Nullable" + type)); - } - if (typeFamily != null) { - typeFamily.put("-" + type, nullableFamily); - } - - if (nextVersionNullableTypeNeeded) { - assert nextVersionTypeTypes != null; - assert nextVersionTypeFamily != null; - nextVersionTypeTypes.put("-" + type, ClassName.get(joinPackage(nextVersionPackage.orElseThrow(), "data.nullables"), "Nullable" + type)); - } - if (nextVersionTypeFamily != null) { - nextVersionTypeFamily.put("-" + type, nullableFamily); - } - - // Check if the array type is needed - boolean arrayTypeNeeded = versionConfiguration.getClassMap() - .values() - .parallelStream() - .map(ParsedClass::getData) - .map(Map::values) - .flatMap(Collection::parallelStream) - .filter((typeZ) -> typeZ.startsWith("§")) - .map((typeZ) -> typeZ.substring(1)) - .anyMatch((typeZ) -> typeZ.equals(type)); - - boolean nextVersionArrayTypeNeeded = nextVersion.filter(s -> s.getClassMap() - .values() - .parallelStream() - .map(ParsedClass::getData) - .map(Map::values) - .flatMap(Collection::parallelStream) - .filter((typeZ) -> typeZ.startsWith("§")) - .map((typeZ) -> typeZ.substring(1)) - .anyMatch((typeZ) -> typeZ.equals(type)) - ).isPresent(); - - if (arrayTypeNeeded) { - registerArrayType(versionPackage, - versionClassType, - typeOptionalSerializers, - typeSerializeStatement, - typeDeserializeStatement, - typeMustGenerateSerializer, - type - ); - typeTypes.put("§" + type, getImmutableArrayType(arrayClassName.get())); - } - if (typeFamily != null) { - typeFamily.put("§" + type, Family.I_TYPE_ARRAY); - } - - if (nextVersionArrayTypeNeeded) { - assert nextVersionTypeTypes != null; - assert nextVersionTypeFamily != null; - nextVersionTypeTypes.put("§" + type, getImmutableArrayType(nextArrayClassName.get())); - } - if (nextVersionTypeFamily != null) { - nextVersionTypeFamily.put("§" + type, Family.I_TYPE_ARRAY); - } - - return new NeededTypes(nullableTypeNeeded, - nextVersionNullableTypeNeeded, - arrayTypeNeeded, - nextVersionArrayTypeNeeded - ); - } - private String capitalizeAll(String text) { StringBuilder sb = new StringBuilder(); boolean firstChar = true; @@ -2830,8 +324,8 @@ public class SourcesGenerator { classBuilder.addMethod(setterMethod.build()); } - private void addField(Builder classBuilder, String fieldName, - TypeName fieldType, boolean isRecord, boolean isFinal, boolean hasSetter) { + private void addField(Builder classBuilder, @NotNull String fieldName, + @NotNull TypeName fieldType, boolean isRecord, boolean isFinal, boolean hasSetter) { if (isFinal && hasSetter) { throw new IllegalStateException(); } @@ -2882,63 +376,4 @@ public class SourcesGenerator { return version.getPackage(basePackageName); } - private String joinPackage(String basePackageName, String packageName) { - if (basePackageName.isBlank()) { - basePackageName = "org.generated"; - } - if (packageName.isBlank()) { - return basePackageName; - } else { - return basePackageName + "." + packageName; - } - } - - private void writeClass(HashSet generatedFilesToDelete, - Path outPath, - String classPackage, - Builder versionsClass) throws IOException { - var sb = new StringBuilder(); - var typeSpec = versionsClass.build(); - var outJavaFile = outPath; - for (String part : classPackage.split("\\.")) { - outJavaFile = outJavaFile.resolve(part); - } - if (Files.notExists(outJavaFile)) { - Files.createDirectories(outJavaFile); - } - outJavaFile = outJavaFile.resolve(typeSpec.name + ".java"); - JavaFile.builder(classPackage, typeSpec).build().writeTo(sb); - String newFile = sb.toString(); - boolean mustWrite; - if (Files.isRegularFile(outJavaFile) && Files.isReadable(outJavaFile)) { - String oldFile = Files.readString(outJavaFile, StandardCharsets.UTF_8); - mustWrite = !oldFile.equals(newFile); - } else { - mustWrite = true; - } - if (mustWrite) { - logger.debug("File {} changed", outJavaFile); - Files.writeString(outJavaFile, newFile, StandardCharsets.UTF_8, TRUNCATE_EXISTING, CREATE, WRITE); - } else { - logger.debug("File {} is the same, unchanged", outJavaFile); - } - markFileAsCreated(generatedFilesToDelete, outPath, outJavaFile); - } - - @Deprecated - private String getVersionVarName(ComputedVersion computedVersion) { - return computedVersion.getVersionVarName(); - } - - private String getVersionShortInt(ComputedVersion version) { - return Integer.toString(version.getVersion()); - } - - private enum Family { - BASIC, - NULLABLE_BASIC, - NULLABLE_OTHER, - I_TYPE_ARRAY, - SPECIAL_NATIVE, GENERIC, NULLABLE_GENERIC, OTHER - } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/Standalone.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/Standalone.java index 04816fe..805e5a7 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/Standalone.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/Standalone.java @@ -7,6 +7,6 @@ public class Standalone { public static void main(String[] args) throws IOException { SourcesGenerator sourcesGenerator = SourcesGenerator.load(Paths.get(args[0])); - sourcesGenerator.generateSources(args[1], Paths.get(args[2]), Boolean.parseBoolean(args[3])); + sourcesGenerator.generateSources(args[1], Paths.get(args[2]), Boolean.parseBoolean(args[3]), false, true); } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/VersionChangeChecker.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/VersionChangeChecker.java index 5c7d448..d5ffda1 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/VersionChangeChecker.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/VersionChangeChecker.java @@ -5,12 +5,24 @@ import java.util.Set; public class VersionChangeChecker { private final Set changedTypes; + private final int version; + private final int latestVersion; - public VersionChangeChecker(Set changedTypes) { + public VersionChangeChecker(Set changedTypes, int version, int latestVersion) { this.changedTypes = changedTypes; + this.version = version; + this.latestVersion = latestVersion; } public boolean checkChanged(String name) { return changedTypes.contains(name); } + + public int getVersion() { + return version; + } + + public int getLatestVersion() { + return latestVersion; + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/VersionedType.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/VersionedType.java index 244c7c5..63b3310 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/VersionedType.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/VersionedType.java @@ -1,15 +1,17 @@ package it.cavallium.data.generator.plugin; -public record VersionedType(String type, int version) { +import java.util.Objects; - public VersionedType withVersion(int version) { - if (version == this.version) { +public record VersionedType(String type, ComputedVersion version) { + + public VersionedType withVersion(ComputedVersion version) { + if (Objects.equals(version, this.version)) { return this; } return new VersionedType(type, version); } - public VersionedType withVersionIfChanged(int version, VersionChangeChecker versionChangeChecker) { + public VersionedType withVersionIfChanged(ComputedVersion version, VersionChangeChecker versionChangeChecker) { if (versionChangeChecker.checkChanged(this.type)) { return withVersion(version); } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenBaseType.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenBaseType.java new file mode 100644 index 0000000..f252cd0 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenBaseType.java @@ -0,0 +1,21 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.TypeSpec; +import it.cavallium.data.generator.plugin.ClassGenerator; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; + +public class GenBaseType extends ClassGenerator { + + public GenBaseType(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + var baseTypeClass = TypeSpec.enumBuilder("BaseType"); + baseTypeClass.addModifiers(Modifier.PUBLIC); + dataModel.getBaseTypesComputed().forEach(baseType -> baseTypeClass.addEnumConstant(baseType.getName())); + return Stream.of(new GeneratedClass(dataModel.getRootPackage(basePackageName), baseTypeClass)); + } +} 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 new file mode 100644 index 0000000..df5e7ec --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenCurrentVersion.java @@ -0,0 +1,191 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeSpec.Builder; +import com.squareup.javapoet.TypeVariableName; +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 java.io.IOException; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; + +public class GenCurrentVersion extends ClassGenerator { + + public GenCurrentVersion(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + var currentVersionDataPackage = dataModel.getCurrentVersion().getDataPackage(basePackageName); + + var currentVersionClass = TypeSpec.classBuilder("CurrentVersion"); + currentVersionClass.addModifiers(Modifier.PUBLIC); + currentVersionClass.addModifiers(Modifier.FINAL); + // Add a static variable for the current version + { + var versionNumberField = FieldSpec.builder(ClassName + .get(dataModel.getCurrentVersion().getPackage(basePackageName), + "Version"), "VERSION").addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC) + .addModifiers(Modifier.FINAL).initializer("new " + dataModel.getCurrentVersion().getPackage(basePackageName) + + ".Version()").build(); + currentVersionClass.addField(versionNumberField); + } + // Check latest version method + { + var isLatestVersionMethod = MethodSpec.methodBuilder("isLatestVersion").addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC).returns(TypeName.BOOLEAN) + .addParameter(ParameterSpec.builder(TypeName.INT, "version").build()) + .addCode("return version == VERSION.getVersion();").build(); + currentVersionClass.addMethod(isLatestVersionMethod); + } + // Get super type classes method + { + var getSuperTypeClasses = MethodSpec.methodBuilder("getSuperTypeClasses").addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC) + .returns(ParameterizedTypeName.get(ClassName.get(Set.class), + ParameterizedTypeName.get(ClassName.get(Class.class), + WildcardTypeName.subtypeOf(ClassName.get(currentVersionDataPackage, "IType"))))) + .addCode("return $T.of(\n", Set.class); + AtomicBoolean isFirst = new AtomicBoolean(true); + dataModel.getSuperTypesComputed(dataModel.getCurrentVersion()).forEach(superType -> { + if (!isFirst.getAndSet(false)) { + getSuperTypeClasses.addCode(",\n"); + } + getSuperTypeClasses.addCode("$T.class", + ClassName.get(dataModel.getVersion(superType).getDataPackage(basePackageName), superType.getName()) + ); + }); + getSuperTypeClasses.addCode("\n);"); + currentVersionClass.addMethod(getSuperTypeClasses.build()); + } + // Get super type subtypes classes method + { + var getSuperTypeSubtypesClasses = MethodSpec.methodBuilder("getSuperTypeSubtypesClasses").addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC) + .returns(ParameterizedTypeName.get(ClassName.get(Set.class), + ParameterizedTypeName.get(ClassName.get(Class.class), + WildcardTypeName.subtypeOf(ClassName.get(currentVersionDataPackage, "IBaseType"))))); + getSuperTypeSubtypesClasses + .addParameter(ParameterSpec.builder(ParameterizedTypeName.get(ClassName.get(Class.class), + WildcardTypeName.subtypeOf(ClassName.get(currentVersionDataPackage, "IType")) + ), "superTypeClass").build()); + getSuperTypeSubtypesClasses.beginControlFlow("return switch (superTypeClass.getCanonicalName())"); + dataModel.getSuperTypesComputed(dataModel.getCurrentVersion()).forEach(superType -> { + getSuperTypeSubtypesClasses.addCode("case \"" + ClassName + .get(currentVersionDataPackage, superType.getName()) + .canonicalName() + "\" -> $T.of(\n", Set.class); + getSuperTypeSubtypesClasses.addCode("$>"); + AtomicBoolean isFirst = new AtomicBoolean(true); + for (ComputedType subType : superType.subTypes()) { + if (!isFirst.getAndSet(false)) { + getSuperTypeSubtypesClasses.addCode(",\n"); + } + getSuperTypeSubtypesClasses.addCode("$T.class", + ClassName.get(currentVersionDataPackage, subType.getName()) + ); + } + getSuperTypeSubtypesClasses.addCode("$<"); + getSuperTypeSubtypesClasses.addCode("\n);\n"); + }); + getSuperTypeSubtypesClasses.addStatement("default -> throw new $T()", IllegalArgumentException.class); + getSuperTypeSubtypesClasses.addCode(CodeBlock.of("$<};")); + currentVersionClass.addMethod(getSuperTypeSubtypesClasses.build()); + } + // UpgradeDataToLatestVersion1 Method + { + var upgradeDataToLatestVersion1MethodBuilder = MethodSpec.methodBuilder("upgradeDataToLatestVersion") + .addTypeVariable(TypeVariableName.get("U", ClassName.get(currentVersionDataPackage, "IBaseType"))) + .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)"); + 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"); + upgradeDataToLatestVersion1MethodBuilder.addStatement("case $T.$N -> $T.INSTANCE.getSerializer(type).deserialize(oldDataInput)", + versions, + versionConfiguration.getVersionVarName(), + ClassName.get(versionConfiguration.getPackage(basePackageName), "Version") + ); + } + var upgradeDataToLatestVersion1Method = upgradeDataToLatestVersion1MethodBuilder + .addStatement("default -> throw new $T(\"Unknown version: \" + oldVersion)", IOException.class) + .addCode(CodeBlock.of("$<});")) + .build(); + currentVersionClass.addMethod(upgradeDataToLatestVersion1Method); + } + // UpgradeDataToLatestVersion2 Method + { + var versionsClassName = ClassName.get(dataModel.getRootPackage(basePackageName), "Versions"); + var upgradeDataToLatestVersion2MethodBuilder = MethodSpec.methodBuilder("upgradeDataToLatestVersion") + .addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC).addModifiers(Modifier.FINAL).addTypeVariable(TypeVariableName.get("T")) + .addTypeVariable(TypeVariableName.get("U", ClassName.get(currentVersionDataPackage, "IBaseType"))) + .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()) { +// Add a case in which the data version deserializes the serialized data and upgrades it + upgradeDataToLatestVersion2MethodBuilder.addCode("case $T.$N: ", + versionsClassName, + versionConfiguration.getVersionVarName() + ); + if (versionConfiguration.isCurrent()) { + // This is the latest version, don't upgrade. + upgradeDataToLatestVersion2MethodBuilder.addStatement("return ($T) data", TypeVariableName.get("U")); + } else { + // Upgrade + ComputedVersion computedVersion = dataModel.getNextVersionOrThrow(versionConfiguration); + upgradeDataToLatestVersion2MethodBuilder + .addStatement( + "data = " + versionConfiguration.getPackage(basePackageName) + + ".Version.upgradeToNextVersion(($T) data)", + ClassName.get(versionConfiguration.getDataPackage(basePackageName), "IBaseType") + ); + } + } + upgradeDataToLatestVersion2MethodBuilder.addStatement("default: throw new $T(\"Unknown version: \" + oldVersion)", IOException.class); + upgradeDataToLatestVersion2MethodBuilder.endControlFlow(); + currentVersionClass.addMethod(upgradeDataToLatestVersion2MethodBuilder.build()); + } + + generateGetClass(dataModel.getCurrentVersion(), currentVersionClass); + + return Stream.of(new GeneratedClass(dataModel.getCurrentVersion().getPackage(basePackageName), currentVersionClass)); + } + + private void generateGetClass(ComputedVersion version, Builder classBuilder) { + var methodBuilder = MethodSpec.methodBuilder("getClass"); + + methodBuilder.addModifiers(Modifier.PUBLIC); + + var baseTypeClassName = ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"); + methodBuilder.addParameter(baseTypeClassName, "type"); + + var iBaseTypeClassName = ClassName.get(version.getDataPackage(basePackageName), "IBaseType"); + methodBuilder.returns(ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(iBaseTypeClassName))); + + methodBuilder.beginControlFlow("return switch (type)"); + dataModel.getBaseTypesComputed(version).forEach(baseType -> { + methodBuilder.addStatement("case $N -> $T.class", baseType.getName(), baseType.getJTypeName(basePackageName)); + }); + methodBuilder.addCode(CodeBlock.of("$<};")); + classBuilder.addMethod(methodBuilder.build()); + } +} 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 new file mode 100644 index 0000000..8c8089d --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataBaseX.java @@ -0,0 +1,110 @@ +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.TypeSpec; +import it.cavallium.data.generator.plugin.ClassGenerator; +import it.cavallium.data.generator.plugin.ComputedTypeBase; +import it.cavallium.data.generator.plugin.ComputedVersion; +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 GenDataBaseX extends ClassGenerator { + + public GenDataBaseX(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 base) { + var type = (ClassName) base.getJTypeName(basePackageName); + var classBuilder = TypeSpec.recordBuilder(type.simpleName()); + + 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 -> { + classBuilder.addSuperinterface(superType.getJTypeName(basePackageName)); + + classBuilder.addMethod(MethodSpec + .methodBuilder("getMetaId$" + superType.getName()) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(Override.class) + .returns(int.class) + .addStatement("return " + superType.subTypes().indexOf(base)) + .build()); + }); + + var ofMethod = MethodSpec + .methodBuilder("of") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); + + base.getData().forEach((fieldName, fieldType) -> { + var fieldTypeName = fieldType.getJTypeName(basePackageName); + + var param = ParameterSpec + .builder(fieldTypeName, fieldName) + .addAnnotation(NotNull.class) + .build(); + + ofMethod.addParameter(param); + classBuilder.addRecordComponent(param); + + var setter = MethodSpec + .methodBuilder("set" + StringUtils.capitalize(fieldName)) + .addModifiers(Modifier.PUBLIC) + .addParameter(ParameterSpec.builder(fieldTypeName, fieldName).addAnnotation(NotNull.class).build()) + .addAnnotation(NotNull.class) + .returns(type); + + if (!fieldTypeName.isPrimitive()) { + setter.addStatement("$T.requireNonNull($N)", Objects.class, fieldName); + } + if (fieldTypeName.isPrimitive() || !deepCheckBeforeCreatingNewEqualInstances) { + setter.addCode("return $N == this.$N ? this : new $T(", fieldName, fieldName, type); + setter.addCode(String.join(", ", base.getData().keySet())); + setter.addStatement(")"); + } else { + setter.addCode("return $T.equals($N, this.$N) ? this : new $T(", Objects.class, fieldName, fieldName, type); + setter.addCode(String.join(", ", base.getData().keySet())); + setter.addStatement(")"); + } + + classBuilder.addMethod(setter.build()); + }); + + classBuilder.addMethod(MethodSpec + .methodBuilder("getBaseType$") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(Override.class) + .returns(baseTypeClass) + .addStatement("return $T.$N", baseTypeClass, base.getName()) + .build()); + + ofMethod.addCode("return new $T(", type); + ofMethod.addCode(String.join(", ", base.getData().keySet())); + ofMethod.addStatement(")"); + ofMethod.returns(type); + classBuilder.addMethod(ofMethod.build()); + + return new GeneratedClass(type.packageName(), classBuilder); + } +} 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 new file mode 100644 index 0000000..72da73a --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataSuperX.java @@ -0,0 +1,85 @@ +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.TypeSpec; +import it.cavallium.data.generator.plugin.ClassGenerator; +import it.cavallium.data.generator.plugin.ComputedTypeSuper; +import it.cavallium.data.generator.plugin.ComputedVersion; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; + +public class GenDataSuperX extends ClassGenerator { + + public GenDataSuperX(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) { + var type = (ClassName) typeSuper.getJTypeName(basePackageName); + var classBuilder = TypeSpec.interfaceBuilder(type.simpleName()); + + classBuilder.addModifiers(Modifier.PUBLIC); + + var baseTypeClass = ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"); + var iBaseTypeClass = ClassName.get(version.getPackage(basePackageName), "IBaseType"); + classBuilder.addSuperinterface(iBaseTypeClass); + + dataModel.getExtendsInterfaces(typeSuper).forEach(superType -> { + classBuilder.addSuperinterface(superType.getJTypeName(basePackageName)); + }); + + Stream + .concat(dataModel.getCommonInterfaceData(typeSuper), dataModel.getCommonInterfaceGetters(typeSuper)) + .forEach(superType -> { + var returnType = superType.getValue().getJTypeName(basePackageName); + var getter = MethodSpec + .methodBuilder(superType.getKey()) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .returns(returnType); + if (!returnType.isPrimitive()) { + getter.addAnnotation(NotNull.class); + } + classBuilder.addMethod(getter.build()); + }); + + dataModel.getCommonInterfaceData(typeSuper).forEach(superType -> { + var returnType = superType.getValue().getJTypeName(basePackageName); + + var setter = MethodSpec + .methodBuilder("set" + StringUtils.capitalize(superType.getKey())) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(ParameterSpec.builder(returnType, "value").addAnnotation(NotNull.class).build()) + .addAnnotation(NotNull.class) + .returns(type); + classBuilder.addMethod(setter.build()); + }); + + dataModel.getSuperTypesOf(typeSuper).forEach(superType -> { + classBuilder.addSuperinterface(superType.getJTypeName(basePackageName)); + }); + + classBuilder.addMethod(MethodSpec + .methodBuilder("getMetaId$" + typeSuper.getName()) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .returns(int.class) + .build()); + + return new GeneratedClass(type.packageName(), classBuilder); + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIBaseType.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIBaseType.java new file mode 100644 index 0000000..90e4f53 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIBaseType.java @@ -0,0 +1,39 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; +import it.cavallium.data.generator.plugin.ClassGenerator; +import it.cavallium.data.generator.plugin.ComputedVersion; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; + +public class GenIBaseType extends ClassGenerator { + + public GenIBaseType(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().stream().map(this::generateClass); + } + + private GeneratedClass generateClass(ComputedVersion version) { + var interfaceBuilder = TypeSpec.interfaceBuilder("IBaseType"); + + interfaceBuilder.addModifiers(Modifier.PUBLIC); + + var iTypeClassName = ClassName.get(version.getDataPackage(basePackageName), "IType"); + var baseTypeClassName = ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"); + interfaceBuilder.addSuperinterface(iTypeClassName); + + interfaceBuilder.addMethod(MethodSpec + .methodBuilder("getBaseType$") + .addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC) + .returns(baseTypeClassName) + .build()); + + return new GeneratedClass(version.getPackage(basePackageName), interfaceBuilder); + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableBaseType.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableBaseType.java new file mode 100644 index 0000000..f55b35e --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableBaseType.java @@ -0,0 +1,39 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; +import it.cavallium.data.generator.plugin.ClassGenerator; +import it.cavallium.data.generator.plugin.ComputedVersion; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; + +public class GenINullableBaseType extends ClassGenerator { + + public GenINullableBaseType(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().stream().map(this::generateClass); + } + + private GeneratedClass generateClass(ComputedVersion version) { + var interfaceBuilder = TypeSpec.interfaceBuilder("INullableBaseType"); + + interfaceBuilder.addModifiers(Modifier.PUBLIC); + + var iNullableITypeClass = ClassName.get(version.getDataNullablesPackage(basePackageName), "INullableIType"); + var baseTypeClass = ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"); + interfaceBuilder.addSuperinterface(iNullableITypeClass); + + interfaceBuilder.addMethod(MethodSpec + .methodBuilder("getBaseType$") + .addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC) + .returns(baseTypeClass) + .build()); + + return new GeneratedClass(version.getDataNullablesPackage(basePackageName), interfaceBuilder); + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableIType.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableIType.java new file mode 100644 index 0000000..cf28031 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableIType.java @@ -0,0 +1,34 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeSpec; +import it.cavallium.data.generator.nativedata.INullable; +import it.cavallium.data.generator.plugin.ClassGenerator; +import it.cavallium.data.generator.plugin.ComputedVersion; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; + +public class GenINullableIType extends ClassGenerator { + + public GenINullableIType(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().stream().map(this::generateClass); + } + + private GeneratedClass generateClass(ComputedVersion version) { + var interfaceBuilder = TypeSpec.interfaceBuilder("INullableIType"); + + interfaceBuilder.addModifiers(Modifier.PUBLIC); + + var iTypeClass = ClassName.get(version.getPackage(basePackageName), "IType"); + var iSuperNullableClass = ClassName.get(INullable.class); + interfaceBuilder.addSuperinterface(iTypeClass); + interfaceBuilder.addSuperinterface(iSuperNullableClass); + + return new GeneratedClass(version.getDataNullablesPackage(basePackageName), interfaceBuilder); + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableSuperType.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableSuperType.java new file mode 100644 index 0000000..140082b --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenINullableSuperType.java @@ -0,0 +1,39 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; +import it.cavallium.data.generator.plugin.ClassGenerator; +import it.cavallium.data.generator.plugin.ComputedVersion; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; + +public class GenINullableSuperType extends ClassGenerator { + + public GenINullableSuperType(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().stream().map(this::generateClass); + } + + private GeneratedClass generateClass(ComputedVersion version) { + var interfaceBuilder = TypeSpec.interfaceBuilder("INullableSuperType"); + + interfaceBuilder.addModifiers(Modifier.PUBLIC); + + var iNullableITypeClass = ClassName.get(version.getDataNullablesPackage(basePackageName), "INullableIType"); + var superTypeClass = ClassName.get(dataModel.getRootPackage(basePackageName), "SuperType"); + interfaceBuilder.addSuperinterface(iNullableITypeClass); + + interfaceBuilder.addMethod(MethodSpec + .methodBuilder("getSuperType$") + .addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC) + .returns(superTypeClass) + .build()); + + return new GeneratedClass(version.getDataNullablesPackage(basePackageName), interfaceBuilder); + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIType.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIType.java new file mode 100644 index 0000000..46c56bd --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIType.java @@ -0,0 +1,31 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeSpec; +import it.cavallium.data.generator.plugin.ClassGenerator; +import it.cavallium.data.generator.plugin.ComputedVersion; +import java.io.Serializable; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; + +public class GenIType extends ClassGenerator { + + public GenIType(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().stream().map(this::generateClass); + } + + private GeneratedClass generateClass(ComputedVersion version) { + var interfaceBuilder = TypeSpec.interfaceBuilder("IType"); + + interfaceBuilder.addModifiers(Modifier.PUBLIC); + + interfaceBuilder.addSuperinterface(ClassName.get(Serializable.class)); + + return new GeneratedClass(version.getPackage(basePackageName), interfaceBuilder); + } +} 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 new file mode 100644 index 0000000..94be0ab --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenIVersion.java @@ -0,0 +1,75 @@ +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.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; + +public class GenIVersion extends ClassGenerator { + + public GenIVersion(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + var iVersionClass = TypeSpec.interfaceBuilder("IVersion"); + 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 + .methodBuilder("getSerializer") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .addTypeVariable(TypeVariableName.get("T", + TypeVariableName.get("B") + )) + .returns(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), TypeVariableName.get("T"))) + .addException(IOException.class) + .addParameter(ParameterSpec + .builder(ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"), "type") + .build()); + iVersionClass.addMethod(getSerializerMethodBuilder.build()); + } + + // Add getVersion method + { + var getVersionMethod = MethodSpec + .methodBuilder("getVersion") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .returns(TypeName.INT) + .build(); + iVersionClass.addMethod(getVersionMethod); + } + + return Stream.of(new GeneratedClass(dataModel.getRootPackage(basePackageName), iVersionClass)); + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenNullableX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenNullableX.java new file mode 100644 index 0000000..d7923f6 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenNullableX.java @@ -0,0 +1,137 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeSpec; +import it.cavallium.data.generator.TypedNullable; +import it.cavallium.data.generator.nativedata.INullable; +import it.cavallium.data.generator.plugin.ClassGenerator; +import it.cavallium.data.generator.plugin.ComputedTypeBase; +import it.cavallium.data.generator.plugin.ComputedTypeNullableVersioned; +import it.cavallium.data.generator.plugin.ComputedTypeSuper; +import it.cavallium.data.generator.plugin.ComputedVersion; +import java.util.List; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class GenNullableX extends ClassGenerator { + + public GenNullableX(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().parallelStream().flatMap(this::generateVersionClasses); + } + + private Stream generateVersionClasses(ComputedVersion version) { + return dataModel + .getComputedTypes(version) + .values() + .stream() + .filter(type -> type instanceof ComputedTypeNullableVersioned) + .map(type -> (ComputedTypeNullableVersioned) type) + .filter(type -> type.getVersion().equals(version)) + .map(type -> generateTypeVersioned(version, type)); + } + + private GeneratedClass generateTypeVersioned(ComputedVersion version, ComputedTypeNullableVersioned computedType) { + var type = (ClassName) computedType.getJTypeName(basePackageName); + var classBuilder = TypeSpec.recordBuilder(type.simpleName()); + + var base = computedType.getBase(); + var baseType = base.getJTypeName(basePackageName); + + classBuilder.addModifiers(Modifier.PUBLIC); + classBuilder.addRecordComponent(ParameterSpec.builder(baseType, "value").build()); + + var iNullableITypeClass = ClassName.get(version.getDataNullablesPackage(basePackageName), "INullableIType"); + var iNullableClass = ClassName.get(INullable.class); + var typedNullable = ParameterizedTypeName.get(ClassName.get(TypedNullable.class), baseType); + + if (base instanceof ComputedTypeSuper computedTypeSuper) { + var iNullableSuperTypeClass = ClassName.get(version.getDataNullablesPackage(basePackageName), "INullableSuperType"); + var superTypeClass = ClassName.get(dataModel.getRootPackage(basePackageName), "SuperType"); + classBuilder.addSuperinterface(iNullableSuperTypeClass); + + classBuilder.addMethod(MethodSpec + .methodBuilder("getSuperType$") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .returns(superTypeClass) + .addStatement("return $T.$N", superTypeClass, base.getName()) + .build()); + } else if (base instanceof ComputedTypeBase computedTypeBase) { + var iNullableBaseTypeClass = ClassName.get(version.getDataNullablesPackage(basePackageName), "INullableBaseType"); + var baseTypeClass = ClassName.get(dataModel.getRootPackage(basePackageName), "BaseType"); + classBuilder.addSuperinterface(iNullableBaseTypeClass); + + classBuilder.addMethod(MethodSpec + .methodBuilder("getBaseType$") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .returns(baseTypeClass) + .addStatement("return $T.$N", baseTypeClass, base.getName()) + .build()); + } else { + throw new UnsupportedOperationException(); + } + + if (version.isCurrent()) { + classBuilder.addSuperinterfaces(List.of(iNullableITypeClass, iNullableClass, typedNullable)); + + classBuilder.addField(FieldSpec.builder(type, "NULL").addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL).initializer("new $T(($T)null)", type, baseType).build()); + + classBuilder.addMethod(MethodSpec + .methodBuilder("of") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(ParameterSpec.builder(baseType, "value").build()) + .addException(ClassName.get(NullPointerException.class)) + .returns(type) + .beginControlFlow("if (value != null)") + .addStatement("return new $T(value)", type) + .nextControlFlow("else") + .addStatement("throw new $T()", NullPointerException.class) + .endControlFlow() + .build()); + + classBuilder.addMethod(MethodSpec + .methodBuilder("ofNullable") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(ParameterSpec.builder(baseType, "value").build()) + .returns(type) + .addStatement("return value != null ? new $T(value) : NULL", type) + .build()); + + classBuilder.addMethod(MethodSpec + .methodBuilder("empty") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(type) + .addStatement("return NULL") + .build()); + + classBuilder.addMethod(MethodSpec + .methodBuilder("or") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(NotNull.class) + .returns(type) + .addParameter(ParameterSpec.builder(type, "fallback").addAnnotation(NotNull.class).build()) + .addStatement("return this.value == null ? fallback : this") + .build()); + + classBuilder.addMethod(MethodSpec + .methodBuilder("getNullable") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addAnnotation(Nullable.class) + .returns(baseType) + .addStatement("return this.value") + .build()); + } + + return new GeneratedClass(type.packageName(), classBuilder); + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSuperType.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSuperType.java new file mode 100644 index 0000000..5b0457a --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenSuperType.java @@ -0,0 +1,21 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.TypeSpec; +import it.cavallium.data.generator.plugin.ClassGenerator; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; + +public class GenSuperType extends ClassGenerator { + + public GenSuperType(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + var superTypeClass = TypeSpec.enumBuilder("SuperType"); + superTypeClass.addModifiers(Modifier.PUBLIC); + dataModel.getSuperTypesComputed().forEach(superType -> superTypeClass.addEnumConstant(superType.getName())); + return Stream.of(new GeneratedClass(dataModel.getRootPackage(basePackageName), superTypeClass)); + } +} 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 new file mode 100644 index 0000000..d4b1797 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersion.java @@ -0,0 +1,228 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +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.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.stream.Stream; +import javax.lang.model.element.Modifier; + +public class GenVersion extends ClassGenerator { + + private static final boolean STRICT_SWITCH = false; + + public GenVersion(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + return dataModel.getVersionsSet().stream().map(this::generateClass); + } + + private GeneratedClass generateClass(ComputedVersion version) { + var classBuilder = TypeSpec.classBuilder("Version"); + + classBuilder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + var iVersionClassName = ClassName.get(dataModel.getRootPackage(basePackageName), "IVersion"); + var iBaseTypeClassName = ClassName.get(version.getDataPackage(basePackageName), "IBaseType"); + classBuilder.addSuperinterface(ParameterizedTypeName.get(iVersionClassName, iBaseTypeClassName)); + + generateVersionField(version, classBuilder); + + generateInstanceField(version, classBuilder); + + generateUpgradeToNextVersion(version, classBuilder); + + generateSerializerInstance(version, classBuilder); + + generateUpgraderInstance(version, classBuilder); + + generateGetSerializer(version, classBuilder); + + generateGetVersion(version, classBuilder); + + return new GeneratedClass(version.getPackage(basePackageName), classBuilder); + } + + /** + * Add a static variable for the current version + */ + private void generateVersionField(ComputedVersion version, Builder classBuilder) { + var versionNumberXField = FieldSpec + .builder(TypeName.INT, "VERSION") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.STATIC) + .addModifiers(Modifier.FINAL) + .initializer("$T." + version.getVersionVarName(), + ClassName.get(dataModel.getRootPackage(basePackageName), "Versions") + ) + .build(); + classBuilder.addField(versionNumberXField); + } + + /** + * Add a static instance for the current version + */ + private void generateInstanceField(ComputedVersion version, Builder classBuilder) { + var versionClassType = ClassName.get(version.getPackage(basePackageName), "Version"); + var versionInstanceField = FieldSpec + .builder(versionClassType, "INSTANCE") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.STATIC) + .addModifiers(Modifier.FINAL) + .initializer("new $T()", versionClassType) + .build(); + classBuilder.addField(versionInstanceField); + } + + private void generateUpgradeToNextVersion(ComputedVersion version, Builder classBuilder) { + var nextVersion = dataModel.getNextVersion(version).orElse(null); + + // Skip upgrade if it's the latest version + if (nextVersion == null) { + return; + } + + var methodBuilder = MethodSpec.methodBuilder("upgradeToNextVersion"); + + methodBuilder.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); + methodBuilder.addException(ClassName.get(IOException.class)); + + var nextIBaseType = ClassName.get(nextVersion.getDataPackage(basePackageName), "IBaseType"); + methodBuilder.returns(nextIBaseType); + + var iBaseTypeClassName = ClassName.get(version.getDataPackage(basePackageName), "IBaseType"); + methodBuilder.addParameter(iBaseTypeClassName, "oldData"); + + methodBuilder.beginControlFlow( "return switch (oldData.getBaseType$$())"); + dataModel.getBaseTypesComputed(version).forEach(baseType -> { + if (baseType.shouldUpgradeAfter(version)) { + var nextBaseType = dataModel.getNextVersion(baseType); + var versionType = ClassName.get(baseType.getVersion().getPackage(basePackageName), "Version"); + methodBuilder.addStatement("case $N -> ($T) $T.$NUpgraderInstance.upgrade(($T) oldData)", + baseType.getName(), + nextBaseType.getJTypeName(basePackageName), + versionType, + baseType.getName(), + baseType.getJTypeName(basePackageName) + ); + } else { + if (STRICT_SWITCH) { + methodBuilder.addStatement("case $N -> ($T) oldData", + baseType.getName(), + baseType.getJTypeName(basePackageName) + ); + } + } + }); + if (!STRICT_SWITCH) { + methodBuilder.addStatement("default -> ($T) oldData", nextIBaseType); + } + methodBuilder.addCode(CodeBlock.of("$<};")); + classBuilder.addMethod(methodBuilder.build()); + } + + private void generateSerializerInstance(ComputedVersion version, Builder classBuilder) { + var versionClassType = ClassName.get(version.getPackage(basePackageName), "Version"); + dataModel.getComputedTypes(version).forEach((typeName, type) -> { + boolean shouldCreateInstanceField; + // Check if the type matches the current version + shouldCreateInstanceField = type instanceof VersionedComputedType versionedComputedType + && versionedComputedType.getVersion().equals(version); + + // Check if the type is custom, and this is the latest version + shouldCreateInstanceField |= version.isCurrent() && type instanceof ComputedTypeCustom; + + if (!shouldCreateInstanceField) { + return; + } + + var serializerFieldLocation = type.getJSerializerInstance(basePackageName); + if (!versionClassType.equals(serializerFieldLocation.className())) { + return; + } + + var serializerClassName = type.getJSerializerName(basePackageName); + + var fieldBuilder = FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), + type.getJTypeName(basePackageName) + ), serializerFieldLocation.fieldName(), Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); + fieldBuilder.initializer("new $T()", serializerClassName); + classBuilder.addField(fieldBuilder.build()); + }); + } + + private void generateUpgraderInstance(ComputedVersion version, Builder classBuilder) { + var versionClassType = ClassName.get(version.getPackage(basePackageName), "Version"); + dataModel.getComputedTypes(version).forEach((typeName, type) -> { + boolean shouldCreateInstanceField = type instanceof VersionedComputedType versionedComputedType + && versionedComputedType.getVersion().equals(version); + if (!shouldCreateInstanceField) { + return; + } + + var upgraderFieldLocation = type.getJUpgraderInstance(basePackageName); + if (!versionClassType.equals(upgraderFieldLocation.className())) { + return; + } + + var upgraderClassName = type.getJUpgraderName(basePackageName); + + var fieldBuilder = FieldSpec.builder(upgraderClassName, + upgraderFieldLocation.fieldName(), + Modifier.PUBLIC, + Modifier.STATIC, + Modifier.FINAL + ); + fieldBuilder.initializer("new $T()", upgraderClassName); + classBuilder.addField(fieldBuilder.build()); + }); + } + + private void generateGetSerializer(ComputedVersion version, Builder classBuilder) { + var methodBuilder = MethodSpec.methodBuilder("getSerializer"); + + methodBuilder.addModifiers(Modifier.PUBLIC); + methodBuilder.addAnnotation(Override.class); + methodBuilder.addException(ClassName.get(IOException.class)); + + var iBaseTypeClassName = ClassName.get(version.getDataPackage(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"))); + + methodBuilder.beginControlFlow("return switch (type)"); + dataModel.getBaseTypesComputed(version).forEach(baseType -> { + var field = baseType.getJSerializerInstance(basePackageName); + methodBuilder.addStatement("case $N -> $T.$N", baseType.getName(), field.className(), field.fieldName()); + }); + methodBuilder.addCode(CodeBlock.of("$<};")); + classBuilder.addMethod(methodBuilder.build()); + } + + private void generateGetVersion(ComputedVersion version, Builder classBuilder) { + classBuilder.addMethod(MethodSpec + .methodBuilder("getVersion") + .addModifiers(Modifier.PUBLIC) + .returns(int.class) + .addStatement("return VERSION") + .addAnnotation(Override.class) + .build()); + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersions.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersions.java new file mode 100644 index 0000000..f00e7ca --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersions.java @@ -0,0 +1,62 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import it.cavallium.data.generator.plugin.ClassGenerator; +import it.cavallium.data.generator.plugin.ComputedVersion; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; + +public class GenVersions extends ClassGenerator { + + public GenVersions(ClassGeneratorParams params) { + super(params); + } + + @Override + protected Stream generateClasses() { + var versionsClass = TypeSpec.classBuilder("Versions"); + versionsClass.addModifiers(Modifier.PUBLIC); + versionsClass.addModifiers(Modifier.FINAL); + var versionsInstances = FieldSpec.builder(ArrayTypeName.of(ClassName.get(dataModel.getRootPackage(basePackageName), "IVersion")), + "VERSIONS", + Modifier.PUBLIC, + Modifier.STATIC, + Modifier.FINAL + ); + List versionsInstancesValue = new ArrayList<>(); + for (ComputedVersion version : dataModel.getVersionsSet()) { + // Add a static variable for this version, containing the normalized version number + var versionNumberField = FieldSpec + .builder(TypeName.INT, version.getVersionVarName()) + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.STATIC) + .addModifiers(Modifier.FINAL) + .initializer(version.getVersionShortInt()) + .build(); + // Add the fields to the class + versionsClass.addField(versionNumberField); + + var versionPackage = version.getPackage(basePackageName); + var versionClassType = ClassName.get(versionPackage, "Version"); + + versionsInstancesValue.add(CodeBlock.builder().add("$T.INSTANCE", versionClassType).build()); + } + versionsInstances.initializer(CodeBlock + .builder() + .add("{\n") + .add(CodeBlock.join(versionsInstancesValue, ",\n")) + .add("\n}") + .build()); + versionsClass.addField(versionsInstances.build()); + return Stream.of(new GeneratedClass(dataModel.getRootPackage(basePackageName), versionsClass)); + } +} diff --git a/data-generator-plugin/src/test/java/it/cavallium/data/generator/plugin/TestGenerator.java b/data-generator-plugin/src/test/java/it/cavallium/data/generator/plugin/TestGenerator.java index 82f6f98..1879681 100644 --- a/data-generator-plugin/src/test/java/it/cavallium/data/generator/plugin/TestGenerator.java +++ b/data-generator-plugin/src/test/java/it/cavallium/data/generator/plugin/TestGenerator.java @@ -1,9 +1,9 @@ package it.cavallium.data.generator.plugin; -import it.cavallium.data.generator.plugin.SourcesGenerator; import java.io.IOException; -import java.nio.file.Files; +import java.nio.file.Path; import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Test; public class TestGenerator { @@ -11,7 +11,7 @@ public class TestGenerator { public void test() throws IOException { var dir = Files.createTempDirectory("data-generator-test"); try { - SourcesGenerator.load(this.getClass().getResourceAsStream("/test.yaml")).generateSources("it.test", dir, false); + SourcesGenerator.load(this.getClass().getResourceAsStream("/test.yaml")).generateSources("it.test", dir, false, true, false); } finally { try { FileUtils.deleteDirectory(dir.toFile()); diff --git a/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/Serializers.java b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/Serializers.java new file mode 100644 index 0000000..aa274b6 --- /dev/null +++ b/data-generator-runtime/src/main/java/it/cavallium/data/generator/nativedata/Serializers.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +public class Serializers { + public static final StringSerializer StringSerializerInstance = new StringSerializer(); + public static final Int52Serializer Int52SerializerInstance = new Int52Serializer(); + + public static final NullableStringSerializer NullableStringSerializerInstance = new NullableStringSerializer(); + public static final NullableInt52Serializer NullableInt52SerializerInstance = new NullableInt52Serializer(); + public static final NullablebooleanSerializer NullablebooleanSerializerInstance = new NullablebooleanSerializer(); + public static final NullablebyteSerializer NullablebyteSerializerInstance = new NullablebyteSerializer(); + public static final NullableshortSerializer NullableshortSerializerInstance = new NullableshortSerializer(); + public static final NullablecharSerializer NullablecharSerializerInstance = new NullablecharSerializer(); + public static final NullableintSerializer NullableintSerializerInstance = new NullableintSerializer(); + public static final NullablelongSerializer NullablelongSerializerInstance = new NullablelongSerializer(); + public static final NullablefloatSerializer NullablefloatSerializerInstance = new NullablefloatSerializer(); + public static final NullabledoubleSerializer NullabledoubleSerializer = new NullabledoubleSerializer(); + + public static final ArrayStringSerializer ArrayStringSerializerInstance = new ArrayStringSerializer(); + public static final ArrayInt52Serializer ArrayInt52SerializerInstance = new ArrayInt52Serializer(); + public static final ArraybooleanSerializer ArraybooleanSerializerInstance = new ArraybooleanSerializer(); + public static final ArraybyteSerializer ArraybyteSerializerInstance = new ArraybyteSerializer(); + public static final ArrayshortSerializer ArrayshortSerializerInstance = new ArrayshortSerializer(); + public static final ArraycharSerializer ArraycharSerializerInstance = new ArraycharSerializer(); + public static final ArrayintSerializer ArrayintSerializerInstance = new ArrayintSerializer(); + public static final ArraylongSerializer ArraylongSerializerInstance = new ArraylongSerializer(); + public static final ArrayfloatSerializer ArrayfloatSerializerInstance = new ArrayfloatSerializer(); + public static final ArraydoubleSerializer ArraydoubleSerializer = new ArraydoubleSerializer(); +}