diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSuper.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSuper.java index d620ad5..0f64ab8 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSuper.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/ComputedTypeSuper.java @@ -1,7 +1,7 @@ package it.cavallium.data.generator.plugin; import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.CodeBlock; import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -110,7 +110,7 @@ public final class ComputedTypeSuper implements VersionedComputedType { } @Override - public TypeName getJUpgraderName(String basePackageName) { + public ClassName getJUpgraderName(String basePackageName) { return ClassName.get(type.version().getSerializersPackage(basePackageName), type.type() + "Upgrader"); } @@ -121,6 +121,16 @@ public final class ComputedTypeSuper implements VersionedComputedType { return new FieldLocation(className, upgraderFieldName); } + @Override + public CodeBlock wrapWithUpgrade(String basePackageName, CodeBlock content, ComputedType next) { + var upgraderInstance = getJUpgraderInstance(basePackageName); + var cb = CodeBlock.builder(); + cb.add(CodeBlock.of("$T.$N.upgrade(", upgraderInstance.className(), upgraderInstance.fieldName())); + cb.add(content); + cb.add(")"); + return VersionedComputedType.super.wrapWithUpgrade(basePackageName, cb.build(), next); + } + @Override public String toString() { return type.type() + " (super, v" + getVersion() + ")"; 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 e1e1ec2..19f0273 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 @@ -30,6 +30,7 @@ import it.cavallium.data.generator.plugin.classgen.GenSerializerNullableX; import it.cavallium.data.generator.plugin.classgen.GenSerializerSuperX; import it.cavallium.data.generator.plugin.classgen.GenSuperType; import it.cavallium.data.generator.plugin.classgen.GenUpgraderBaseX; +import it.cavallium.data.generator.plugin.classgen.GenUpgraderSuperX; import it.cavallium.data.generator.plugin.classgen.GenVersion; import it.cavallium.data.generator.plugin.classgen.GenVersions; import it.unimi.dsi.fastutil.booleans.BooleanList; @@ -190,6 +191,8 @@ public class SourcesGenerator { new GenUpgraderBaseX(genParams).run(); + new GenUpgraderSuperX(genParams).run(); + // Update the hash at the end Files.writeString(hashPath, basePackageName + '\n' + useRecordBuilders + '\n' + curHash + '\n', StandardCharsets.UTF_8, TRUNCATE_EXISTING, WRITE, CREATE); diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataBaseX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataBaseX.java index c8281b9..8e4ef22 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataBaseX.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenDataBaseX.java @@ -51,7 +51,7 @@ public class GenDataBaseX extends ClassGenerator { dataModel.getSuperTypesOf(base, false).forEach(superType -> { classBuilder.addMethod(MethodSpec .methodBuilder("getMetaId$" + superType.getName()) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) .returns(int.class) .addStatement("return " + superType.subTypes().indexOf(base)) @@ -60,7 +60,7 @@ public class GenDataBaseX extends ClassGenerator { var ofMethod = MethodSpec .methodBuilder("of") - .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); + .addModifiers(Modifier.PUBLIC, Modifier.STATIC); base.getData().forEach((fieldName, fieldType) -> { var fieldTypeName = fieldType.getJTypeName(basePackageName); diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderBaseX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderBaseX.java index dad5e62..95fa261 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderBaseX.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderBaseX.java @@ -8,8 +8,10 @@ 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 it.cavallium.data.generator.DataInitializer; import it.cavallium.data.generator.DataUpgrader; import it.cavallium.data.generator.plugin.ClassGenerator; import it.cavallium.data.generator.plugin.ComputedType; @@ -86,10 +88,6 @@ public class GenUpgraderBaseX extends ClassGenerator { method.addParameter(ParameterSpec.builder(typeBaseClassName, "data").addAnnotation(NotNull.class).build()); - nextTypeBase.getData().forEach((fieldName, fieldType) -> { - method.addStatement("$T $N", fieldType.getJTypeName(basePackageName), fieldName); - }); - List expectedResultFields = nextTypeBase.getData().keySet().stream().toList(); AtomicInteger nextInitializerStaticFieldId = new AtomicInteger(); @@ -122,11 +120,15 @@ public class GenUpgraderBaseX extends ClassGenerator { var computedTypes = dataModel.getComputedTypes(nextTypeBase.getVersion()); var newFieldType = Objects.requireNonNull(computedTypes.get(fixType(newDataConfiguration.type))); var initializerClass = ClassName.bestGuess(newDataConfiguration.initializer); + var genericInitializerClass = ParameterizedTypeName.get(ClassName.get(DataInitializer.class), + newFieldType.getJTypeName(basePackageName).box() + ); var initializerName = createInitializerStaticField(nextInitializerStaticFieldId, initializerStaticFieldNames, classBuilder, - initializerClass + initializerClass, + genericInitializerClass ); return new Field(newDataConfiguration.to, newFieldType, CodeBlock.of("$N.initialize()", initializerName), i + 1); @@ -168,11 +170,16 @@ public class GenUpgraderBaseX extends ClassGenerator { var cb = CodeBlock.builder(); var newFieldType = Objects .requireNonNull(dataModel.getComputedTypes(version).get(fixType(upgradeDataConfiguration.type))); + var genericUpgraderClass = ParameterizedTypeName.get(ClassName.get(DataUpgrader.class), + fieldType.getJTypeName(basePackageName).box(), + newFieldType.getJTypeName(basePackageName).box() + ); var upgraderName = createUpgraderStaticField(nextUpgraderStaticFieldId, upgraderStaticFieldNames, classBuilder, - upgraderClass + upgraderClass, + genericUpgraderClass ); cb.add("($T) $N.upgrade(($T) ", @@ -219,13 +226,14 @@ public class GenUpgraderBaseX extends ClassGenerator { private String createInitializerStaticField(AtomicInteger nextInitializerStaticFieldId, HashMap initializerStaticFieldNames, Builder classBuilder, - ClassName initializerClass) { + ClassName initializerClass, + TypeName genericInitializerClass) { var ref = initializerClass.reflectionName(); var initializerName = initializerStaticFieldNames.get(ref); if (initializerName == null) { initializerName = "I" + nextInitializerStaticFieldId.getAndIncrement(); classBuilder.addField(FieldSpec - .builder(initializerClass, initializerName) + .builder(genericInitializerClass, initializerName) .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) .initializer("new $T()", initializerClass) .build()); @@ -237,13 +245,14 @@ public class GenUpgraderBaseX extends ClassGenerator { private String createUpgraderStaticField(AtomicInteger nextUpgraderStaticFieldId, HashMap upgraderStaticFieldNames, Builder classBuilder, - ClassName upgraderClass) { + ClassName upgraderClass, + TypeName genericUpgraderClass) { var ref = upgraderClass.reflectionName(); var upgraderName = upgraderStaticFieldNames.get(ref); if (upgraderName == null) { upgraderName = "U" + nextUpgraderStaticFieldId.getAndIncrement(); classBuilder.addField(FieldSpec - .builder(upgraderClass, upgraderName) + .builder(genericUpgraderClass, upgraderName) .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) .initializer("new $T()", upgraderClass) .build()); diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderSuperX.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderSuperX.java new file mode 100644 index 0000000..0008ffd --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenUpgraderSuperX.java @@ -0,0 +1,98 @@ +package it.cavallium.data.generator.plugin.classgen; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeSpec.Builder; +import it.cavallium.data.generator.DataUpgrader; +import it.cavallium.data.generator.plugin.ClassGenerator; +import it.cavallium.data.generator.plugin.ComputedType; +import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; +import it.cavallium.data.generator.plugin.ComputedTypeSuper; +import it.cavallium.data.generator.plugin.ComputedVersion; +import java.io.IOException; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; +import org.jetbrains.annotations.NotNull; + +public class GenUpgraderSuperX extends ClassGenerator { + + public GenUpgraderSuperX(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().isCurrent() && type.getVersion().equals(version)) + .map(type -> generateTypeVersioned(version, type)); + } + + private GeneratedClass generateTypeVersioned(ComputedVersion version, ComputedTypeSuper typeSuper) { + ClassName upgraderClassName = typeSuper.getJUpgraderName(basePackageName); + ClassName typeBaseClassName = typeSuper.getJTypeName(basePackageName); + ComputedTypeSuper nextTypeSuper = dataModel.getNextVersion(typeSuper); + + var classBuilder = TypeSpec.classBuilder(upgraderClassName.simpleName()); + + classBuilder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + classBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(DataUpgrader.class), + typeBaseClassName, + nextTypeSuper.getJTypeName(basePackageName) + )); + + generateUpgradeMethod(version, typeSuper, nextTypeSuper, classBuilder); + + return new GeneratedClass(upgraderClassName.packageName(), classBuilder); + } + + private void generateUpgradeMethod(ComputedVersion version, ComputedTypeSuper typeSuper, + ComputedTypeSuper nextTypeSuper, + Builder classBuilder) { + var method = MethodSpec.methodBuilder("upgrade"); + + method.addModifiers(Modifier.PUBLIC, Modifier.FINAL); + method.addException(IOException.class); + + ClassName typeSuperClassName = typeSuper.getJTypeName(basePackageName); + ClassName nextTypeSuperClassName = nextTypeSuper.getJTypeName(basePackageName); + method.returns(nextTypeSuperClassName); + method.addAnnotation(NotNull.class); + + method.addParameter(ParameterSpec.builder(typeSuperClassName, "data").addAnnotation(NotNull.class).build()); + + method.beginControlFlow("return switch (data.getMetaId$$$N())", typeSuper.getName()); + int i = 0; + for (ComputedType subType : typeSuper.subTypes()) { + method.addCode("case $L -> ", i); + method.addStatement(upgradeSubTypeToType(subType, CodeBlock.of("($T) data", subType.getJTypeName(basePackageName)), nextTypeSuper)); + i++; + } + method.addStatement("default -> throw new $T(data.getMetaId$$$N())", IndexOutOfBoundsException.class, typeSuper.getName()); + method.addCode("$<};\n"); + + classBuilder.addMethod(method.build()); + } + + private CodeBlock upgradeSubTypeToType(ComputedType subType, + CodeBlock codeBlock, + ComputedTypeSuper nextTypeSuper) { + while (subType instanceof VersionedComputedType versionedComputedType + && versionedComputedType.getVersion().compareTo(nextTypeSuper.getVersion()) < 0) { + var currentFieldType = subType; + var nextFieldType = dataModel.getNextVersion(currentFieldType); + codeBlock = currentFieldType.wrapWithUpgrade(basePackageName, codeBlock, nextFieldType); + subType = nextFieldType; + } + return codeBlock; + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersion.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersion.java index 62b1a82..fe05bd7 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersion.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/plugin/classgen/GenVersion.java @@ -15,6 +15,7 @@ import it.cavallium.data.generator.plugin.ClassGenerator; import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; import it.cavallium.data.generator.plugin.ComputedTypeBase; import it.cavallium.data.generator.plugin.ComputedTypeCustom; +import it.cavallium.data.generator.plugin.ComputedTypeSuper; import it.cavallium.data.generator.plugin.ComputedVersion; import java.io.IOException; import java.util.Objects; @@ -171,7 +172,8 @@ public class GenVersion extends ClassGenerator { 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 ComputedTypeBase versionedComputedType + boolean shouldCreateInstanceField = type instanceof VersionedComputedType versionedComputedType + && (type instanceof ComputedTypeBase || type instanceof ComputedTypeSuper) && versionedComputedType.getVersion().equals(version) && !version.isCurrent(); if (!shouldCreateInstanceField) { return;