From 379c9065ee6f780e66ee6edf51b7741702e6fbe1 Mon Sep 17 00:00:00 2001 From: Andrea Cavalli Date: Tue, 27 Apr 2021 00:08:19 +0200 Subject: [PATCH] Track data type during update --- .../data/generator/SourcesGenerator.java | 644 +++++++++++++++--- .../generator/nativedata/NullableString.java | 2 +- .../generator/nativedata/Nullableboolean.java | 2 +- .../generator/nativedata/Nullablebyte.java | 2 +- .../generator/nativedata/Nullablechar.java | 2 +- .../generator/nativedata/Nullabledouble.java | 7 +- .../generator/nativedata/Nullableint.java | 2 +- .../generator/nativedata/Nullablelong.java | 2 +- .../generator/nativedata/Nullableshort.java | 2 +- 9 files changed, 580 insertions(+), 85 deletions(-) diff --git a/src/main/java/it/cavallium/data/generator/SourcesGenerator.java b/src/main/java/it/cavallium/data/generator/SourcesGenerator.java index 5056fd8..3efeac6 100644 --- a/src/main/java/it/cavallium/data/generator/SourcesGenerator.java +++ b/src/main/java/it/cavallium/data/generator/SourcesGenerator.java @@ -18,6 +18,7 @@ import it.cavallium.data.generator.nativedata.IGenericNullable; import it.cavallium.data.generator.nativedata.Int52Serializer; import it.cavallium.data.generator.nativedata.StringSerializer; import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.io.DataInput; import java.io.DataOutput; @@ -179,6 +180,23 @@ public class SourcesGenerator { writeClass(outPath, joinPackage(basePackageName, ""), basicTypeClass); } + // Create the GenericType class + { + var genericTypeClass = TypeSpec.enumBuilder("GenericType"); + genericTypeClass.addModifiers(Modifier.PUBLIC); + for (Entry stringVersionConfigurationEntry : configuration.versions.entrySet()) { + String k = stringVersionConfigurationEntry.getKey(); + VersionConfiguration value = stringVersionConfigurationEntry.getValue(); + for (String superTypeName : value.superTypes.keySet()) { + if (!genericTypeClass.enumConstants.containsKey(superTypeName)) { + genericTypeClass.addEnumConstant(superTypeName); + } + } + } + // Save the resulting class in the main package + writeClass(outPath, joinPackage(basePackageName, ""), genericTypeClass); + } + // Create the IVersion class { var iVersionClass = TypeSpec.interfaceBuilder("IVersion"); @@ -442,7 +460,25 @@ public class SourcesGenerator { "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, @@ -461,8 +497,26 @@ public class SourcesGenerator { "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", @@ -482,8 +536,10 @@ public class SourcesGenerator { 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, @@ -534,12 +590,16 @@ public class SourcesGenerator { typeTypes.put("-" + specialNativeType, ClassName.get("it.cavallium.data.generator.nativedata", "Nullable" + specialNativeType) ); + typeFamily.put("-" + specialNativeType, Family.NULLABLE_OTHER); typeTypes.put("§" + specialNativeType, ArrayTypeName.of(typeTypes.get(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, ArrayTypeName.of(typeTypes.get(specialNativeType))); + nextVersionTypeFamily.put("§" + specialNativeType, Family.OTHER); } typeOptionalSerializers.put("-" + specialNativeType, ClassName.get("it.cavallium.data.generator.nativedata", @@ -599,6 +659,7 @@ public class SourcesGenerator { Stream .concat(versionConfiguration.classes.keySet().stream(), versionConfiguration.superTypes.keySet().stream()) .forEach((type) -> { + boolean isBasic = versionConfiguration.classes.containsKey(type); typeOptionalSerializers.put(type, ClassName.get(joinPackage(versionPackage, "serializers"), type + "Serializer") ); @@ -616,11 +677,14 @@ public class SourcesGenerator { ); 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); } NeededTypes neededTypes = registerNeededTypes(versionConfiguration, + !isBasic ? Family.GENERIC : Family.BASIC, type, nextVersion, nextVersionPackage, @@ -631,7 +695,9 @@ public class SourcesGenerator { typeDeserializeStatement, typeMustGenerateSerializer, typeTypes, + typeFamily, nextVersionTypeTypes, + nextVersionTypeFamily, () -> ClassName.get(joinPackage(versionPackage, "data"), type), () -> ClassName.get(joinPackage(nextVersionPackage.orElseThrow(), "data"), type) ); @@ -656,12 +722,15 @@ public class SourcesGenerator { ); typeMustGenerateSerializer.put(key, false); typeTypes.put(key, ClassName.bestGuess(customTypeConfiguration.javaClass)); + typeFamily.put(key, Family.OTHER); if (nextVersion.isPresent()) { nextVersionTypeTypes.put(key, ClassName.bestGuess(customTypeConfiguration.javaClass)); + nextVersionTypeFamily.put(key, Family.OTHER); } var arrayClassName = ClassName.bestGuess(customTypeConfiguration.javaClass); var neededTypes = registerNeededTypes(versionConfiguration, + Family.OTHER, key, nextVersion, nextVersionPackage, @@ -672,7 +741,9 @@ public class SourcesGenerator { typeDeserializeStatement, typeMustGenerateSerializer, typeTypes, + typeFamily, nextVersionTypeTypes, + nextVersionTypeFamily, () -> arrayClassName, () -> arrayClassName ); @@ -786,14 +857,25 @@ public class SourcesGenerator { // Create the nullable X types classes { var typeType = typeTypes.get(substring); + var family = typeFamily.get(substring); var nullableTypeType = typeTypes.get("-" + substring); var nullableTypeClass = TypeSpec.classBuilder("Nullable" + capitalize(substring)); nullableTypeClass.addModifiers(Modifier.PUBLIC); nullableTypeClass.addModifiers(Modifier.FINAL); nullableTypeClass.addAnnotation(EqualsAndHashCode.class); nullableTypeClass.addAnnotation(ToString.class); + if (family == Family.BASIC) { + nullableTypeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data.nullables"), + "INullableBasicType" + )); + } + if (family == Family.GENERIC) { + nullableTypeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data.nullables"), + "INullableGenericType" + )); + } nullableTypeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data.nullables"), - "INullableBasicType" + "INullableIType" )); nullableTypeClass.addSuperinterface(IGenericNullable.class); var constructor = MethodSpec.constructorBuilder(); @@ -889,6 +971,28 @@ public class SourcesGenerator { getDollarNullableMethod.returns(typeType); getDollarNullableMethod.addStatement("return this.getNullable()"); nullableTypeClass.addMethod(getDollarNullableMethod.build()); + if (family == Family.BASIC) { + var getBasicType = MethodSpec.methodBuilder("getBasicType$"); + getBasicType.addModifiers(Modifier.PUBLIC); + getBasicType.addModifiers(Modifier.FINAL); + getBasicType.addException(NullPointerException.class); + getBasicType.addAnnotation(NotNull.class); + getBasicType.addAnnotation(NonNull.class); + getBasicType.returns(ClassName.get(joinPackage(basePackageName, ""), "BasicType")); + getBasicType.addStatement("return $T." + capitalize(substring), ClassName.get(joinPackage(basePackageName, ""), "BasicType")); + nullableTypeClass.addMethod(getBasicType.build()); + } + if (family == Family.GENERIC) { + var getBasicType = MethodSpec.methodBuilder("getGenericType$"); + getBasicType.addModifiers(Modifier.PUBLIC); + getBasicType.addModifiers(Modifier.FINAL); + getBasicType.addException(NullPointerException.class); + getBasicType.addAnnotation(NotNull.class); + getBasicType.addAnnotation(NonNull.class); + getBasicType.returns(ClassName.get(joinPackage(basePackageName, ""), "GenericType")); + getBasicType.addStatement("return $T." + capitalize(substring), ClassName.get(joinPackage(basePackageName, ""), "GenericType")); + nullableTypeClass.addMethod(getBasicType.build()); + } try { writeClass(outPath, joinPackage(versionPackage, "data.nullables"), nullableTypeClass); @@ -1072,6 +1176,14 @@ public class SourcesGenerator { deserializeMethod.addException(IOException.class); Object2IntLinkedOpenHashMap currentVarNumber = new Object2IntLinkedOpenHashMap<>( basicTypeConfiguration.getData().size()); + Object2ObjectLinkedOpenHashMap currentVarTypeName = new Object2ObjectLinkedOpenHashMap<>( + basicTypeConfiguration.getData().size()); + Object2ObjectLinkedOpenHashMap currentVarTypeClass = new Object2ObjectLinkedOpenHashMap<>( + basicTypeConfiguration.getData().size()); + Object2ObjectLinkedOpenHashMap currentVarFamily = new Object2ObjectLinkedOpenHashMap<>( + basicTypeConfiguration.getData().size()); + ObjectOpenHashSet currentVarUpgraded = new ObjectOpenHashSet<>( + basicTypeConfiguration.getData().size()); ObjectOpenHashSet currentVarDeleted = new ObjectOpenHashSet<>(); currentVarNumber.defaultReturnValue(-1); deserializeMethod.addStatement("$T.requireNonNull(data)", Objects.class); @@ -1079,6 +1191,10 @@ public class SourcesGenerator { 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.get" + capitalize(k) + "()"); } @@ -1117,6 +1233,10 @@ public class SourcesGenerator { "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); } @@ -1128,6 +1248,18 @@ public class SourcesGenerator { { 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) + "$$" @@ -1139,6 +1271,10 @@ public class SourcesGenerator { "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); } @@ -1199,6 +1335,14 @@ public class SourcesGenerator { 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); break; case "new-data": @@ -1211,6 +1355,12 @@ public class SourcesGenerator { 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); @@ -1256,16 +1406,94 @@ public class SourcesGenerator { for (var e : currentVarNumber.object2IntEntrySet()) { String key = e.getKey(); int number = e.getIntValue(); - if (!currentVarDeleted.contains(key)) { + 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); 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); - deserializeMethod.addStatement( - "$T $$field$$" + (number + 1) + "$$" + key + " = ($T) " + "upgradeUnknownField($$field$$" - + number + "$$" + key + ")", toType, toTypeBoxed); + + switch (currentFamily) { + case BASIC: + case GENERIC: + deserializeMethod.addCode(buildStatementUpgradeBasicType( + versionPackage, + nextVersionPackage.get(), + number, + key, + toTypeName, + toFamily, + toType, + toTypeBoxed + )); + break; + case I_TYPE_ARRAY: + deserializeMethod.addCode(buildStatementUpgradeITypeArrayField( + versionPackage, + nextVersionPackage.get(), + number, + key, + toTypeName, + toFamily, + toType, + toTypeBoxed + )); + break; + case NULLABLE_BASIC: + deserializeMethod.addCode(buildStatementUpgradeNullableBasicField( + versionPackage, + nextVersionPackage.get(), + number, + key, + toTypeName, + toFamily, + toType, + toTypeBoxed, + nextVersionTypeTypes.get(toTypeName.substring(1)) + )); + break; + case NULLABLE_GENERIC: + deserializeMethod.addCode(buildStatementUpgradeNullableGenericField( + versionPackage, + nextVersionPackage.get(), + number, + key, + toTypeName, + toFamily, + toType, + toTypeBoxed, + nextVersionTypeTypes.get(toTypeName.substring(1)) + )); + break; + case NULLABLE_OTHER: + deserializeMethod.addCode(buildStatementUpgradeNullableOtherField( + versionPackage, + nextVersionPackage.get(), + number, + key, + toTypeName, + toFamily, + toType, + toTypeBoxed + )); + break; + default: + throw new IllegalStateException("Unexpected value: " + currentFamily); + } } } } @@ -1283,73 +1511,6 @@ public class SourcesGenerator { deserializeMethod.addStatement(")"); upgraderClass.addMethod(deserializeMethod.build()); } - // Create the upgradeUnknownField method - { - var upgradeUnknownField = MethodSpec.methodBuilder("upgradeUnknownField"); - upgradeUnknownField.addModifiers(Modifier.PRIVATE); - upgradeUnknownField.addModifiers(Modifier.STATIC); - upgradeUnknownField.addModifiers(Modifier.FINAL); - upgradeUnknownField.returns(Object.class); - upgradeUnknownField.addParameter(ParameterSpec - .builder(Object.class, "value") - .addAnnotation(NotNull.class) - .addAnnotation(NonNull.class) - .build()); - upgradeUnknownField.addException(IOException.class); - - var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); - var oldIBasicType = ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"); - var oldIType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); - var oldINullableBasicType = ClassName.get(joinPackage(versionPackage, "data.nullables"), - "INullableBasicType" - ); - var newIBasicType = ClassName.get(joinPackage(nextVersionPackage.get(), "data"), "IBasicType"); - upgradeUnknownField.addStatement("$T.requireNonNull(value)", Objects.class); - upgradeUnknownField.addStatement("Class type = ((Object) value).getClass()"); - upgradeUnknownField.beginControlFlow("if (value instanceof $T)", oldIBasicType); - upgradeUnknownField.addStatement("return ($T) $T.upgradeToNextVersion(($T) value)", - newIBasicType, - oldVersionType, - oldIBasicType - ); - upgradeUnknownField.nextControlFlow("else if (value instanceof $T)", oldINullableBasicType); - upgradeUnknownField.addStatement("var content = (($T) value).$$getNullable()", IGenericNullable.class); - upgradeUnknownField.addStatement("$T newContent", Object.class); - upgradeUnknownField.beginControlFlow("if (content instanceof $T)", oldIBasicType); - upgradeUnknownField.addStatement("newContent = ($T) $T.upgradeToNextVersion(($T) content)", - newIBasicType, - oldVersionType, - oldIBasicType - ); - upgradeUnknownField.nextControlFlow("else"); - upgradeUnknownField.addStatement("newContent = content"); - upgradeUnknownField.endControlFlow(); - upgradeUnknownField.addStatement("return $T.INSTANCE.createNullableOf(type.getSimpleName(), newContent)", - ClassName.get(nextVersionPackage.get(), "Version") - ); - upgradeUnknownField.nextControlFlow( - "else if (type.isArray() && $T.class.isAssignableFrom(type.getComponentType()))", - oldIType - ); - upgradeUnknownField.addStatement("int arrayLength = $T.getLength(value)", Array.class); - upgradeUnknownField.addStatement( - "Object newArray = $T.INSTANCE.createArrayOf(type.getComponentType().getSimpleName(), arrayLength)", - ClassName.get(nextVersionPackage.get(), "Version") - ); - upgradeUnknownField.beginControlFlow("for (int i = 0; i < arrayLength; i++)"); - upgradeUnknownField.addStatement("var item = $T.get(value, i)", Array.class); - upgradeUnknownField.addStatement("var updatedItem = $T.upgradeToNextVersion(($T) item)", - oldVersionType, - oldIBasicType - ); - upgradeUnknownField.addStatement("$T.set(newArray, i, updatedItem)", Array.class); - upgradeUnknownField.endControlFlow(); - upgradeUnknownField.addStatement("return newArray"); - upgradeUnknownField.nextControlFlow("else"); - upgradeUnknownField.addStatement("return value"); - upgradeUnknownField.endControlFlow(); - upgraderClass.addMethod(upgradeUnknownField.build()); - } // Save the resulting class in the main package try { writeClass(outPath, joinPackage(versionPackage, "upgraders"), upgraderClass); @@ -1642,7 +1803,7 @@ public class SourcesGenerator { .methodBuilder("createNullableOf") .addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.FINAL) - .returns(ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableBasicType")) + .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()) @@ -1666,6 +1827,64 @@ public class SourcesGenerator { .endControlFlow(); versionClass.addMethod(createNullableOfMethod.build()); } + // Add createNullableOfBasicType method + { + var createNullableOfBasicTypeMethod = MethodSpec + .methodBuilder("createNullableOfBasicType") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL) + .returns(ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableBasicType")) + .addException(IOException.class) + .addParameter(ParameterSpec.builder(ClassName.get(joinPackage(basePackageName, ""), "BasicType"), "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)) { + createNullableOfBasicTypeMethod.addStatement("case " + type + ": return $T.ofNullable(($T) content)", + typeTypes.get("-" + type), + typeTypes.get(type) + ); + } + } + } + createNullableOfBasicTypeMethod + .beginControlFlow("default: ") + .addStatement("throw new $T(\"Unknown nullable type: \" + type)", IOException.class) + .endControlFlow() + .endControlFlow(); + versionClass.addMethod(createNullableOfBasicTypeMethod.build()); + } + // Add createNullableOfGenericType method + { + var createNullableOfGenericTypeMethod = MethodSpec + .methodBuilder("createNullableOfGenericType") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL) + .returns(ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableGenericType")) + .addException(IOException.class) + .addParameter(ParameterSpec.builder(ClassName.get(joinPackage(basePackageName, ""), "GenericType"), "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)) { + createNullableOfGenericTypeMethod.addStatement("case " + type + ": return $T.ofNullable(($T) content)", + typeTypes.get("-" + type), + typeTypes.get(type) + ); + } + } + } + createNullableOfGenericTypeMethod + .beginControlFlow("default: ") + .addStatement("throw new $T(\"Unknown nullable type: \" + type)", IOException.class) + .endControlFlow() + .endControlFlow(); + versionClass.addMethod(createNullableOfGenericTypeMethod.build()); + } // Add createArrayOf method { var createArrayOfMethod = MethodSpec @@ -1747,12 +1966,32 @@ public class SourcesGenerator { } } - // Create the interface INullableBasicType + // 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(IGenericNullable.class); + try { + writeClass(outPath, joinPackage(versionPackage, "data.nullables"), inullableITypeInterface); + } catch (IOException e) { + throw new IOError(e); + } + } + + // Create the interface INullableBasicType + { + var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableIType"); var inullableBasicTypeInterface = TypeSpec.interfaceBuilder("INullableBasicType"); inullableBasicTypeInterface.addModifiers(Modifier.PUBLIC); inullableBasicTypeInterface.addSuperinterface(iTypeInterfaceType); + var getBasicTypeMethod = MethodSpec + .methodBuilder("getBasicType$") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .returns(ClassName.get(joinPackage(basePackageName, ""), "BasicType")); + inullableBasicTypeInterface.addMethod(getBasicTypeMethod.build()); try { writeClass(outPath, joinPackage(versionPackage, "data.nullables"), inullableBasicTypeInterface); } catch (IOException e) { @@ -1760,16 +1999,35 @@ public class SourcesGenerator { } } + // Create the interface INullableGenericType + { + var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableIType"); + var inullablegenericTypeInterface = TypeSpec.interfaceBuilder("INullableGenericType"); + inullablegenericTypeInterface.addModifiers(Modifier.PUBLIC); + inullablegenericTypeInterface.addSuperinterface(iTypeInterfaceType); + var getBasicTypeMethod = MethodSpec + .methodBuilder("getGenericType$") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .returns(ClassName.get(joinPackage(basePackageName, ""), "GenericType")); + inullablegenericTypeInterface.addMethod(getBasicTypeMethod.build()); + try { + writeClass(outPath, joinPackage(versionPackage, "data.nullables"), inullablegenericTypeInterface); + } catch (IOException e) { + throw new IOError(e); + } + } + // Create the interfaces { for (Entry> superType : versionConfiguration.superTypes.entrySet()) { String type = superType.getKey(); Set superTypeConfiguration = superType.getValue(); - var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); + var iBasicTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"); var typeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), type); var typeInterface = TypeSpec.interfaceBuilder(type); typeInterface.addModifiers(Modifier.PUBLIC); - typeInterface.addSuperinterface(iTypeInterfaceType); + typeInterface.addSuperinterface(iBasicTypeInterfaceType); var getMetaTypeMethod = MethodSpec .methodBuilder("getMetaId$" + type) .addModifiers(Modifier.PUBLIC) @@ -1990,6 +2248,206 @@ public class SourcesGenerator { } } + private CodeBlock buildStatementUpgradeBasicType( + 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 oldIBasicType = ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"); + var upgradeBasicTypeField = MethodSpec.methodBuilder("upgradeBasicTypeField"); + upgradeBasicTypeField.addModifiers(Modifier.PRIVATE); + upgradeBasicTypeField.addModifiers(Modifier.STATIC); + upgradeBasicTypeField.addModifiers(Modifier.FINAL); + upgradeBasicTypeField.returns(Object.class); + upgradeBasicTypeField.addParameter(ParameterSpec + .builder(oldIBasicType, "value") + .addAnnotation(NotNull.class) + .addAnnotation(NonNull.class) + .build()); + upgradeBasicTypeField.addException(IOException.class); + + var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); + var oldIType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); + var oldINullableBasicType = ClassName.get(joinPackage(versionPackage, "data.nullables"), + "INullableBasicType" + ); + upgradeBasicTypeField.addStatement("$T.requireNonNull(value)", Objects.class); + deserializeMethod.addStatement("$N = ($T) $T.upgradeToNextVersion(($T) value)", + resultFieldName, + toTypeBoxed, + oldVersionType, + oldIBasicType + ); + + 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 oldINullableGenericType = ClassName.get(joinPackage(versionPackage, "data.nullables"), + "INullableGenericType" + ); + + var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); + var oldIBasicType = ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"); + 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 toGenericType) { + 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 oldINullableGenericType = ClassName.get(joinPackage(versionPackage, "data.nullables"), + "INullableGenericType" + ); + + var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); + var oldIBasicType = ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"); + 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 = ($T) $T.upgradeToNextVersion(($T) content)", + toGenericType, + oldVersionType, + oldIBasicType + ); + 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 toBasicType) { + 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 oldINullableBasicType = ClassName.get(joinPackage(versionPackage, "data.nullables"), + "INullableBasicType" + ); + + var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); + var oldIBasicType = ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"); + 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 = ($T) $T.upgradeToNextVersion(($T) content)", + toBasicType, + oldVersionType, + oldIBasicType + ); + 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 oldIType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); + var oldITypeArray = ArrayTypeName.of(oldIType); + + var newIType = ClassName.get(joinPackage(nextVersionPackage, "data"), "IType"); + var newITypeArray = ArrayTypeName.of(newIType); + + var oldVersionType = ClassName.get(joinPackage(versionPackage, ""), "Version"); + var oldIBasicType = ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"); + var oldINullableBasicType = ClassName.get(joinPackage(versionPackage, "data.nullables"), + "INullableBasicType" + ); + deserializeMethod.addStatement("$T.requireNonNull(value)", Objects.class); + deserializeMethod.addStatement("var newArray = new $T[value.length]", ((ArrayTypeName) toTypeBoxed).componentType); + deserializeMethod.beginControlFlow("for (int i = 0; i < value.length; i++)"); + deserializeMethod.addStatement("var item = value[i]", Array.class); + deserializeMethod.addStatement("var updatedItem = ($T) $T.upgradeToNextVersion(($T) item)", + ((ArrayTypeName) toTypeBoxed).componentType, + oldVersionType, + oldIBasicType + ); + deserializeMethod.addStatement("newArray[i] = updatedItem"); + deserializeMethod.endControlFlow(); + deserializeMethod.addStatement("$N = newArray", resultFieldName); + + + deserializeMethod.endControlFlow(); + return deserializeMethod.build(); + } + private static String getSpecialNativePackage(String specialNativeType) { //noinspection SwitchStatementWithTooFewBranches switch (specialNativeType) { @@ -2055,6 +2513,7 @@ public class SourcesGenerator { @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public NeededTypes registerNeededTypes(VersionConfiguration versionConfiguration, + Family family, String type, Optional nextVersion, Optional nextVersionPackage, @@ -2064,7 +2523,10 @@ public class SourcesGenerator { HashMap typeSerializeStatement, HashMap typeDeserializeStatement, HashMap typeMustGenerateSerializer, - HashMap typeTypes, @Nullable HashMap nextVersionTypeTypes, + HashMap typeTypes, + HashMap typeFamily, + @Nullable HashMap nextVersionTypeTypes, + @Nullable HashMap nextVersionTypeFamily, Supplier arrayClassName, Supplier nextArrayClassName) { // Check if the nullable type is needed @@ -2090,6 +2552,20 @@ public class SourcesGenerator { .anyMatch((typeZ) -> typeZ.equals(type))) .isPresent(); + Family nullableFamily; + switch (family) { + case BASIC: + nullableFamily = Family.NULLABLE_BASIC; + break; + case GENERIC: + nullableFamily = Family.NULLABLE_GENERIC; + break; + case OTHER: + nullableFamily = Family.NULLABLE_OTHER; + break; + default: + throw new IllegalStateException("Unexpected value: " + family); + } if (nullableTypeNeeded) { typeOptionalSerializers.put("-" + type, @@ -2101,10 +2577,13 @@ public class SourcesGenerator { .add("$T.Nullable" + type + "SerializerInstance.deserialize(dataInput)", versionClassType).build()); typeMustGenerateSerializer.put("-" + type, true); typeTypes.put("-" + type, ClassName.get(joinPackage(versionPackage, "data.nullables"), "Nullable" + type)); + typeFamily.put("-" + type, nullableFamily); } if (nextVersionNullableTypeNeeded) { assert nextVersionTypeTypes != null; + assert nextVersionTypeFamily != null; nextVersionTypeTypes.put("-" + type, ClassName.get(joinPackage(nextVersionPackage.orElseThrow(), "data.nullables"), "Nullable" + type)); + nextVersionTypeFamily.put("-" + type, nullableFamily); } // Check if the array type is needed @@ -2139,10 +2618,13 @@ public class SourcesGenerator { type ); typeTypes.put("§" + type, ArrayTypeName.of(arrayClassName.get())); + typeFamily.put("§" + type, Family.I_TYPE_ARRAY); } if (nextVersionArrayTypeNeeded) { assert nextVersionTypeTypes != null; + assert nextVersionTypeFamily != null; nextVersionTypeTypes.put("§" + type, ArrayTypeName.of(nextArrayClassName.get())); + nextVersionTypeFamily.put("§" + type, Family.I_TYPE_ARRAY); } return new NeededTypes(nullableTypeNeeded, @@ -2362,4 +2844,12 @@ public class SourcesGenerator { } return version; } + + private enum Family { + BASIC, + NULLABLE_BASIC, + NULLABLE_OTHER, + I_TYPE_ARRAY, + SPECIAL_NATIVE, GENERIC, NULLABLE_GENERIC, OTHER + } } diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullableString.java b/src/main/java/it/cavallium/data/generator/nativedata/NullableString.java index 6dde480..9233429 100644 --- a/src/main/java/it/cavallium/data/generator/nativedata/NullableString.java +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullableString.java @@ -68,7 +68,7 @@ public class NullableString implements Serializable, IGenericNullable { } @Override - public Object $getNullable() { + public String $getNullable() { return this.getNullable(); } diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullableboolean.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullableboolean.java index a8c6bca..d7061de 100644 --- a/src/main/java/it/cavallium/data/generator/nativedata/Nullableboolean.java +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullableboolean.java @@ -52,7 +52,7 @@ public class Nullableboolean implements Serializable, IGenericNullable { } @Override - public Object $getNullable() { + public Boolean $getNullable() { return this.getNullable(); } diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullablebyte.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullablebyte.java index 0186d59..ee7a22c 100644 --- a/src/main/java/it/cavallium/data/generator/nativedata/Nullablebyte.java +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullablebyte.java @@ -56,7 +56,7 @@ public class Nullablebyte implements Serializable, IGenericNullable { } @Override - public Object $getNullable() { + public Byte $getNullable() { return this.getNullable(); } diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullablechar.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullablechar.java index c39564b..1159bd8 100644 --- a/src/main/java/it/cavallium/data/generator/nativedata/Nullablechar.java +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullablechar.java @@ -56,7 +56,7 @@ public class Nullablechar implements Serializable, IGenericNullable { } @Override - public Object $getNullable() { + public Character $getNullable() { return this.getNullable(); } diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullabledouble.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullabledouble.java index 37047bf..27f21cd 100644 --- a/src/main/java/it/cavallium/data/generator/nativedata/Nullabledouble.java +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullabledouble.java @@ -5,7 +5,7 @@ import java.util.Objects; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class Nullabledouble implements Serializable { +public class Nullabledouble implements Serializable, IGenericNullable { private static final long serialVersionUID = 1L; @@ -51,6 +51,11 @@ public class Nullabledouble implements Serializable { } } + @Override + public Double $getNullable() { + return this.getNullable(); + } + @Nullable public Double getNullable() { return value; diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullableint.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullableint.java index cabea4d..4419758 100644 --- a/src/main/java/it/cavallium/data/generator/nativedata/Nullableint.java +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullableint.java @@ -52,7 +52,7 @@ public class Nullableint implements Serializable, IGenericNullable { } @Override - public Object $getNullable() { + public Integer $getNullable() { return this.getNullable(); } diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullablelong.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullablelong.java index 42f3cde..8c1bcce 100644 --- a/src/main/java/it/cavallium/data/generator/nativedata/Nullablelong.java +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullablelong.java @@ -52,7 +52,7 @@ public class Nullablelong implements Serializable, IGenericNullable { } @Override - public Object $getNullable() { + public Long $getNullable() { return this.getNullable(); } diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullableshort.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullableshort.java index 8b839b4..c4c425b 100644 --- a/src/main/java/it/cavallium/data/generator/nativedata/Nullableshort.java +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullableshort.java @@ -52,7 +52,7 @@ public class Nullableshort implements Serializable, IGenericNullable { } @Override - public Object $getNullable() { + public Short $getNullable() { return this.getNullable(); }