diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/ClassConfiguration.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/ClassConfiguration.java index a84add5..7926f3a 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/ClassConfiguration.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/ClassConfiguration.java @@ -3,7 +3,7 @@ package it.cavallium.data.generator; import java.util.LinkedHashMap; import java.util.Objects; -public class ClassConfiguration { +public final class ClassConfiguration { public String stringRepresenter; @@ -36,4 +36,13 @@ public class ClassConfiguration { hash += ConfigUtils.hashCode(data); return hash; } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public ClassConfiguration clone() { + var cc = new ClassConfiguration(); + cc.stringRepresenter = stringRepresenter; + cc.data = new LinkedHashMap<>(data); + return cc; + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/ComputedVersion.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/ComputedVersion.java new file mode 100644 index 0000000..716fd4e --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/ComputedVersion.java @@ -0,0 +1,88 @@ +package it.cavallium.data.generator; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class ComputedVersion { + + 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) { + this.details = value.details; + this.transformations = value.transformations; + this.version = version; + this.current = current; + this.name = versionName; + this.classMap = classMap; + } + + public int getVersion() { + return version; + } + + public String getName() { + return name; + } + + public Map getClassMap() { + return classMap; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ComputedVersion that = (ComputedVersion) o; + return Objects.equals(details, that.details) + && Objects.equals(transformations, that.transformations); + } + + @Override + public int hashCode() { + int hash = 0; + hash += ConfigUtils.hashCode(details); + hash += ConfigUtils.hashCode(transformations); + return hash; + } + + public String getPackage(String basePackageName) { + if (current) { + return joinPackage(basePackageName, "current"); + } else { + return joinPackage(basePackageName, "v" + getVersionCompleteInt()); + } + } + + public String getVersionVarName() { + return "V" + version; + } + + private String getVersionCompleteInt() { + 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; + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/CustomTypesConfiguration.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/CustomTypesConfiguration.java index 7d96913..86155f8 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/CustomTypesConfiguration.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/CustomTypesConfiguration.java @@ -5,7 +5,7 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import java.util.Objects; -public class CustomTypesConfiguration { +public final class CustomTypesConfiguration { private String javaClass; public String serializer; @@ -50,4 +50,13 @@ public class CustomTypesConfiguration { hash += ConfigUtils.hashCode(serializer); return hash; } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public CustomTypesConfiguration clone() { + var c = new CustomTypesConfiguration(); + c.javaClass = this.javaClass; + c.serializer = this.serializer; + return c; + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/DataModel.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/DataModel.java new file mode 100644 index 0000000..78dcd0a --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/DataModel.java @@ -0,0 +1,399 @@ +package it.cavallium.data.generator; + +import static java.util.Objects.requireNonNull; + +import com.google.common.collect.Lists; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +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.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.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; + +public class DataModel { + + private static final List NATIVE_TYPES = List.of("String", + "boolean", + "short", + "char", + "int", + "long", + "float", + "double", + "byte", + "Int52" + ); + + private final int currentVersion; + private final Int2ObjectMap> classConfig; + private final int hash; + private final Map interfacesData; + private final Int2ObjectMap versions; + private final Map> superTypes; + private final Map customTypes; + + public DataModel(int hash, + String currentVersionKey, + Map interfacesData, + Map baseTypesData, + Map> superTypesData, + Map customTypesData, + Map rawVersions) { + + this.hash = hash; + + if (rawVersions.isEmpty()) { + throw new IllegalArgumentException("No defined versions"); + } + + if (!rawVersions.containsKey(currentVersionKey)) { + throw new IllegalArgumentException("Current version " + currentVersionKey + " is not defined in versions!"); + } + + // Check if there are multiple root versions + String rootVersion = rawVersions.entrySet().stream() + .filter(e -> e.getValue().previousVersion == null) + .map(Entry::getKey) + .collect(toSingleton(DataModel::throwMultiRootVersions)); + + // Check if multiple versions depend on the same version + rawVersions.entrySet().stream() + .filter(v -> v.getValue().previousVersion != null) + .collect(Collectors.groupingBy(v -> v.getValue().previousVersion)) + .entrySet() + .stream() + .filter(x -> x.getValue().size() > 1) + .forEach(x -> { + throw new IllegalArgumentException("Multiple versions depend on version " + x.getKey() + ": " + x.getValue() + .stream().map(Entry::getKey).collect(Collectors.joining(", "))); + }); + + // Create the next versions map + Map nextVersionMap = rawVersions.keySet().stream() + .map(version -> Map.entry(version, rawVersions + .entrySet() + .stream() + .filter(x -> Objects.equals(version, x.getValue().previousVersion)) + .map(Entry::getKey) + .collect(toOptional(nextVersions -> throwMultiNextVersions(version, nextVersions)))) + ) + .filter(x -> x.getValue().isPresent()) + .map(x -> Map.entry(x.getKey(), x.getValue().get())) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + + // Build versions sequence + List rawVersionsSequence = new ArrayList<>(); + int versionsCount = 0; + IntList versionsSequence = IntStream.range(0, versionsCount).boxed().collect(Collectors.toCollection(IntArrayList::new)); + Int2ObjectMap versionToName = new Int2ObjectOpenHashMap<>(); + Object2IntMap nameToVersion = new Object2IntOpenHashMap<>(); + { + String lastVersion = null; + String nextVersion = rootVersion; + while (nextVersion != null) { + rawVersionsSequence.add(nextVersion); + lastVersion = nextVersion; + versionToName.put(versionsCount, nextVersion); + nameToVersion.put(nextVersion, versionsCount); + nextVersion = getNextVersion(nextVersionMap, nextVersion); + versionsCount++; + } + if (!Objects.equals(lastVersion, currentVersionKey)) { + throw new IllegalArgumentException("Last version " + lastVersion + + " is different than the defined current version: " + currentVersionKey); + } + } + + int latestVersion = versionsCount - 1; + + // Collect all existing base types + List baseTypes = new ArrayList<>(baseTypesData.keySet()); + + // Collect all existing super types + List superTypes = new ArrayList<>(superTypesData.keySet()); + + // Collect all custom types + List customTypes = new ArrayList<>(customTypesData.keySet()); + + List allTypes = Stream.concat(Stream.concat(Stream.concat(baseTypes.stream(), superTypes.stream()), + customTypes.stream()), NATIVE_TYPES.stream()) + .distinct() + .toList(); + + Stream.concat(Stream.concat(Stream.concat(baseTypes.stream(), superTypes.stream()), + customTypes.stream()), NATIVE_TYPES.stream()) + .collect(Collectors.groupingBy(Function.identity())) + .values() + .stream() + .filter(x -> x.size() > 1) + .forEach(x -> { + var type = x.get(0); + throw new IllegalArgumentException("Type " + type + " has been defined more than once (check base, super, and custom types)!"); + }); + + // Compute the numeric versions map + Int2ObjectMap versions = new Int2ObjectOpenHashMap<>(); + rawVersions.forEach((k, v) -> versions.put(nameToVersion.getInt(k), new ParsedVersion(v))); + + // Compute the cartesian product of version * base type + record VersionAndType(int version, String type) {} + List versionAndBaseType = Lists.cartesianProduct(versionsSequence, baseTypes).stream() + .map(tuple -> new VersionAndType((Integer) tuple.get(0), (String) tuple.get(1))) + .toList(); + + Int2ObjectMap> computedClassConfig = new Int2ObjectOpenHashMap<>(); + for (int versionIndex = 0; versionIndex < versionsCount; versionIndex++) { + if (versionIndex == 0) { + computedClassConfig.put(0, baseTypesData.entrySet().stream() + .map(e -> Map.entry(e.getKey(), new ParsedClass(e.getValue()))) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)) + ); + } else { + var version = versions.get(versionIndex); + Map prevVersionConfiguration + = requireNonNull(computedClassConfig.get(versionIndex - 1)); + Map newVersionConfiguration = prevVersionConfiguration.entrySet().stream() + .map(entry -> Map.entry(entry.getKey(), entry.getValue().clone())) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + + for (VersionTransformation rawTransformation : version.transformations) { + TransformationConfiguration transformation; + var transformCoordinate = "Transformation at index " + + version.transformations.indexOf(rawTransformation) + " in version " + + versionToName.get(versionIndex); + try { + transformation = rawTransformation.getTransformation(); + } catch (Exception ex) { + throw new IllegalArgumentException(transformCoordinate + " is not consistent", ex); + } + final String transformName = transformation.getTransformName(); + switch (transformName) { + case "move-data" -> { + var t = (MoveDataConfiguration) transformation; + var transformClass = newVersionConfiguration.get(t.transformClass); + if (transformClass == null) { + throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + + t.transformClass); + } + String definition = transformClass.data.remove(t.from); + if (definition == null) { + throw new IllegalArgumentException(transformCoordinate + " refers to an unknown field: " + t.from); + } + var prevDef = transformClass.data.put(t.to, definition); + if (prevDef != null) { + throw new IllegalArgumentException( + transformCoordinate + " tries to overwrite the existing field \"" + t.to + "\" of value \"" + + prevDef + "\" with the field \"" + t.from + "\" of type \"" + definition + "\""); + } + } + case "new-data" -> { + var t = (NewDataConfiguration) transformation; + var transformClass = newVersionConfiguration.get(t.transformClass); + if (transformClass == null) { + throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + + t.transformClass); + } + if (!allTypes.contains(extractTypeName(t.type))) { + throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.type); + } + var prevDef = transformClass.data.put(t.to, fixType(t.type)); + if (prevDef != null) { + throw new IllegalArgumentException(transformCoordinate + " tries to overwrite the existing field \"" + + t.to + "\" of value \"" + prevDef + + "\" with the new type \"" + t.type + "\""); + } + } + case "remove-data" -> { + var t = (RemoveDataConfiguration) transformation; + var transformClass = newVersionConfiguration.get(t.transformClass); + if (transformClass == null) { + throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + + t.transformClass); + } + var prevDef = transformClass.data.remove(t.from); + if (prevDef == null) { + throw new IllegalArgumentException(transformCoordinate + " tries to remove the nonexistent field \"" + + t.from + "\""); + } + } + case "upgrade-data" -> { + var t = (UpgradeDataConfiguration) transformation; + var transformClass = newVersionConfiguration.get(t.transformClass); + if (transformClass == null) { + throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + + t.transformClass); + } + if (!allTypes.contains(extractTypeName(t.type))) { + throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.type); + } + String prevDefinition = transformClass.data.put(t.from, fixType(t.type)); + if (prevDefinition == null) { + throw new IllegalArgumentException(transformCoordinate + " refers to an unknown field: " + t.from); + } + } + default -> throw new IllegalArgumentException("Unknown transform name: "+ transformName); + }; + } + + computedClassConfig.put(versionIndex, newVersionConfiguration); + } + } + + this.classConfig = computedClassConfig; + 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(); + }, + Int2ObjectOpenHashMap::new + )); + this.currentVersion = versionsCount - 1; + this.superTypes = superTypesData; + this.customTypes = customTypesData; + } + + @Nullable + public static String getNextVersion(Map versionsSequence, String version) { + return versionsSequence.get(version); + } + + private static RuntimeException throwMultiNextVersions(String version, List nextVersions) { + return new IllegalArgumentException("Found many next versions of version " + version + ":" + + String.join(", ", nextVersions)); + } + + private static RuntimeException throwMultiRootVersions(List rootVersions) { + return new IllegalArgumentException("Found many root versions: " + String.join(", ", rootVersions)); + } + + public static Collector toSingleton() { + return toSingleton(x -> new IllegalStateException()); + } + + public static Collector toSingleton(Function, RuntimeException> exceptionGenerator) { + return Collectors.collectingAndThen( + Collectors.toList(), + list -> { + if (list.size() != 1) { + throw exceptionGenerator.apply(list); + } + return list.get(0); + } + ); + } + + public static Collector> toOptional() { + return toOptional(x -> new IllegalStateException()); + } + + public static Collector> toOptional(Function, RuntimeException> exceptionGenerator) { + return Collectors.collectingAndThen( + Collectors.toList(), + list -> { + if (list.size() > 1) { + throw exceptionGenerator.apply(list); + } + if (list.isEmpty()) { + return Optional.empty(); + } + return Optional.of(list.get(0)); + } + ); + } + + public int computeHash() { + return hash; + } + + public Set> getInterfacesSet() { + return interfacesData.entrySet(); + } + + public static String fixType(String fieldType) { + if (fieldType.endsWith("[]") && fieldType.startsWith("-")) { + throw new UnsupportedOperationException("Arrays cannot be null"); + } + if (fieldType.endsWith("[]")) { + return "§" + fieldType.substring(0, fieldType.length() - 2); + } else { + return fieldType; + } + } + + /** + * Get the base type + * X --> X + * -X --> X + * X[] --> X + */ + public static String extractTypeName(String fieldType) { + if (fieldType.endsWith("[]") && fieldType.startsWith("-")) { + throw new UnsupportedOperationException("Arrays cannot be null"); + } + if (fieldType.endsWith("[]")) { + return fieldType.substring(0, fieldType.length() - 2); + } else if (fieldType.startsWith("-")) { + return fieldType.substring(1); + } else { + return fieldType; + } + } + + public ObjectCollection getVersionsSet() { + return this.versions.values(); + } + + public int getCurrentVersionNumber() { + return currentVersion; + } + + public ComputedVersion getCurrentVersion() { + return versions.get(currentVersion); + } + + public Map> getSuperTypes() { + return this.superTypes; + } + + public Optional getNextVersion(ComputedVersion versionConfiguration) { + var nextVersion = versions.get(versionConfiguration.getVersion() + 1); + return Optional.ofNullable(nextVersion); + } + + public ComputedVersion getNextVersionOrThrow(ComputedVersion versionConfiguration) { + return Objects.requireNonNull(versions.get(versionConfiguration.getVersion() + 1)); + } + + public Map getInterfaces() { + return interfacesData; + } + + public Map getCustomTypes() { + return customTypes; + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/MoveDataConfiguration.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/MoveDataConfiguration.java index a1690eb..927d678 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/MoveDataConfiguration.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/MoveDataConfiguration.java @@ -2,7 +2,7 @@ package it.cavallium.data.generator; import java.util.Objects; -public class MoveDataConfiguration implements TransformationConfiguration { +public final class MoveDataConfiguration implements TransformationConfiguration { public String transformClass; public String from; @@ -40,4 +40,14 @@ public class MoveDataConfiguration implements TransformationConfiguration { hash += ConfigUtils.hashCode(to); return hash; } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public MoveDataConfiguration clone() { + var c = new MoveDataConfiguration(); + if (this.transformClass != null) c.transformClass = this.transformClass; + if (this.from != null) c.from = this.from; + if (this.to != null) c.to = this.to; + return c; + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/NewDataConfiguration.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/NewDataConfiguration.java index b1253d0..8990d70 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/NewDataConfiguration.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/NewDataConfiguration.java @@ -6,6 +6,7 @@ public class NewDataConfiguration implements TransformationConfiguration { public String transformClass; public String to; + public String type; public String initializer; @Override @@ -27,10 +28,8 @@ public class NewDataConfiguration implements TransformationConfiguration { return false; } NewDataConfiguration that = (NewDataConfiguration) o; - return Objects.equals(transformClass, that.transformClass) && Objects.equals(to, that.to) && Objects.equals( - initializer, - that.initializer - ); + return Objects.equals(transformClass, that.transformClass) && Objects.equals(to, that.to) + && Objects.equals(type, that.type) && Objects.equals(initializer, that.initializer); } @Override @@ -38,7 +37,19 @@ public class NewDataConfiguration implements TransformationConfiguration { int hash = 0; hash += ConfigUtils.hashCode(transformClass); hash += ConfigUtils.hashCode(to); + hash += ConfigUtils.hashCode(type); hash += ConfigUtils.hashCode(initializer); return hash; } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public NewDataConfiguration clone() { + var c = new NewDataConfiguration(); + if (this.transformClass != null) c.transformClass = this.transformClass; + if (this.initializer != null) c.initializer = this.initializer; + if (this.to != null) c.to = this.to; + if (this.type != null) c.type = this.type; + return c; + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/ParsedClass.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/ParsedClass.java new file mode 100644 index 0000000..b13d33a --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/ParsedClass.java @@ -0,0 +1,68 @@ +package it.cavallium.data.generator; + +import static it.cavallium.data.generator.DataModel.fixType; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.stream.Collectors; + +public final class ParsedClass { + + public String stringRepresenter; + + public LinkedHashMap data; + + public ParsedClass(ClassConfiguration baseTypesData) { + this.stringRepresenter = baseTypesData.stringRepresenter; + if (baseTypesData.data != null) { + this.data = baseTypesData.data.entrySet().stream() + .map(e -> Map.entry(e.getKey(), fixType(e.getValue()))) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> { + throw new IllegalStateException(); + }, LinkedHashMap::new)); + } + } + + public ParsedClass() { + + } + + public String getStringRepresenter() { + return stringRepresenter; + } + + public LinkedHashMap getData() { + return data; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ParsedClass that = (ParsedClass) o; + return Objects.equals(stringRepresenter, that.stringRepresenter) && Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + int hash = 0; + hash += ConfigUtils.hashCode(stringRepresenter); + hash += ConfigUtils.hashCode(data); + return hash; + } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public ParsedClass clone() { + var cc = new ParsedClass(); + if (this.stringRepresenter != null) cc.stringRepresenter = this.stringRepresenter; + cc.data = new LinkedHashMap<>(data); + return cc; + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/ParsedInterface.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/ParsedInterface.java new file mode 100644 index 0000000..bd41315 --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/ParsedInterface.java @@ -0,0 +1,50 @@ +package it.cavallium.data.generator; + +import static it.cavallium.data.generator.DataModel.fixType; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public class ParsedInterface { + + public Set extendInterfaces = new HashSet<>(); + public Map commonData = new HashMap<>(); + public Map commonGetters = new HashMap<>(); + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ParsedInterface that = (ParsedInterface) o; + return Objects.equals(extendInterfaces, that.extendInterfaces) && Objects.equals(commonData, that.commonData) + && Objects.equals(commonGetters, that.commonGetters); + } + + @Override + public int hashCode() { + int hash = 0; + hash += ConfigUtils.hashCode(extendInterfaces); + hash += ConfigUtils.hashCode(commonData); + hash += ConfigUtils.hashCode(commonGetters); + return hash; + } + + public ParsedInterface(InterfaceDataConfiguration value) { + if (value.extendInterfaces != null) this.extendInterfaces = value.extendInterfaces; + if (value.commonData != null) { + this.commonData = value.commonData; + this.commonData.replaceAll((k, v) -> fixType(v)); + } + if (value.commonGetters != null) { + this.commonGetters = value.commonGetters; + this.commonGetters.replaceAll((k, v) -> fixType(v)); + } + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/ParsedVersion.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/ParsedVersion.java new file mode 100644 index 0000000..32cc3af --- /dev/null +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/ParsedVersion.java @@ -0,0 +1,43 @@ +package it.cavallium.data.generator; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class ParsedVersion { + + public DetailsConfiguration details; + public List transformations; + + public ParsedVersion(VersionConfiguration versionConfiguration) { + this.details = versionConfiguration.details; + if (versionConfiguration.transformations != null) { + this.transformations = versionConfiguration.transformations.stream().map(VersionTransformation::clone).toList(); + } + } + + public ParsedVersion() { + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ParsedVersion that = (ParsedVersion) o; + return Objects.equals(details, that.details) && Objects.equals(transformations, that.transformations); + } + + @Override + public int hashCode() { + int hash = 0; + hash += ConfigUtils.hashCode(details); + hash += ConfigUtils.hashCode(transformations); + return hash; + } +} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/RemoveDataConfiguration.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/RemoveDataConfiguration.java index 2af1c36..4d7a937 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/RemoveDataConfiguration.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/RemoveDataConfiguration.java @@ -36,4 +36,13 @@ public class RemoveDataConfiguration implements TransformationConfiguration { hash += ConfigUtils.hashCode(from); return hash; } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public RemoveDataConfiguration clone() { + var c = new RemoveDataConfiguration(); + if (this.transformClass != null) c.transformClass = this.transformClass; + if (this.from != null) c.from = this.from; + return c; + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGenerator.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGenerator.java index 6faa87a..f69f8e5 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGenerator.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGenerator.java @@ -73,11 +73,12 @@ public class SourcesGenerator { private static final Logger logger = LoggerFactory.getLogger(SourcesGenerator.class); private static final boolean OVERRIDE_ALL_NULLABLE_METHODS = false; - private final SourcesGeneratorConfiguration configuration; + private final DataModel dataModel; private SourcesGenerator(InputStream yamlDataStream) { Yaml yaml = new Yaml(); - this.configuration = yaml.loadAs(yamlDataStream, SourcesGeneratorConfiguration.class); + var configuration = yaml.loadAs(yamlDataStream, SourcesGeneratorConfiguration.class); + this.dataModel = configuration.buildDataModel(); } public static SourcesGenerator load(InputStream yamlData) { @@ -111,7 +112,7 @@ public class SourcesGenerator { basePackageNamePath = basePackageNamePathPartial; } var hashPath = basePackageNamePath.resolve(".hash"); - var curHash = computeHash(this.configuration); + var curHash = dataModel.computeHash(); if (Files.isRegularFile(hashPath) && Files.isReadable(hashPath)) { var lines = Files.readAllLines(hashPath, StandardCharsets.UTF_8); if (lines.size() >= 3) { @@ -150,27 +151,6 @@ public class SourcesGenerator { StandardCharsets.UTF_8, TRUNCATE_EXISTING, WRITE, CREATE); markFileAsCreated(generatedFilesToDelete, outPath, hashPath); - // Fix the configuration - for (Entry interfacesDatum : configuration.interfacesData.entrySet()) { - String k = interfacesDatum.getKey(); - InterfaceDataConfiguration value = interfacesDatum.getValue(); - value.commonData.replaceAll((field, fieldType) -> fixType(fieldType)); - } - for (Entry interfacesDatum : configuration.interfacesData.entrySet()) { - String name = interfacesDatum.getKey(); - InterfaceDataConfiguration value = interfacesDatum.getValue(); - value.commonGetters.replaceAll((field, fieldType) -> fixType(fieldType)); - } - for (Entry stringVersionConfigurationEntry : configuration.versions.entrySet()) { - String k = stringVersionConfigurationEntry.getKey(); - VersionConfiguration config = stringVersionConfigurationEntry.getValue(); - for (Entry entry : config.classes.entrySet()) { - String clazz = entry.getKey(); - ClassConfiguration classConfiguration = entry.getValue(); - classConfiguration.getData().replaceAll((field, fieldType) -> fixType(fieldType)); - } - } - // Create the Versions class var versionsClass = TypeSpec.classBuilder("Versions"); versionsClass.addModifiers(Modifier.PUBLIC); @@ -182,21 +162,19 @@ public class SourcesGenerator { Modifier.FINAL ); List versionsInstancesValue = new ArrayList<>(); - for (Entry stringVersionConfigurationEntry : configuration.versions.entrySet()) { - String version = stringVersionConfigurationEntry.getKey(); - VersionConfiguration value = stringVersionConfigurationEntry.getValue(); + for (ComputedVersion value : dataModel.getVersionsSet()) { // Add a static variable for this version, containing the normalized version number var versionNumberField = FieldSpec - .builder(TypeName.INT, getVersionVarName(version)) + .builder(TypeName.INT, getVersionVarName(value)) .addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.STATIC) .addModifiers(Modifier.FINAL) - .initializer(getVersionShortInt(version)) + .initializer(getVersionShortInt(value)) .build(); // Add the fields to the class versionsClass.addField(versionNumberField); - var versionPackage = getVersionPackage(configuration.currentVersion, basePackageName, version); + var versionPackage = value.getPackage(basePackageName); var versionClassType = ClassName.get(joinPackage(versionPackage, ""), "Version"); versionsInstancesValue.add(CodeBlock.builder().add("$T.INSTANCE", versionClassType).build()); @@ -215,10 +193,8 @@ public class SourcesGenerator { { var basicTypeClass = TypeSpec.enumBuilder("BasicType"); basicTypeClass.addModifiers(Modifier.PUBLIC); - for (Entry stringVersionConfigurationEntry : configuration.versions.entrySet()) { - String k = stringVersionConfigurationEntry.getKey(); - VersionConfiguration value = stringVersionConfigurationEntry.getValue(); - for (String basicTypeName : value.classes.keySet()) { + for (var value : dataModel.getVersionsSet()) { + for (String basicTypeName : value.getClassMap().keySet()) { if (!basicTypeClass.enumConstants.containsKey(basicTypeName)) { basicTypeClass.addEnumConstant(basicTypeName); } @@ -232,10 +208,8 @@ public class SourcesGenerator { { 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()) { + for (var value : dataModel.getVersionsSet()) { + for (String superTypeName : dataModel.getSuperTypes().keySet()) { if (!genericTypeClass.enumConstants.containsKey(superTypeName)) { genericTypeClass.addEnumConstant(superTypeName); } @@ -298,6 +272,8 @@ public class SourcesGenerator { writeClass(generatedFilesToDelete, outPath, joinPackage(basePackageName, ""), iVersionClass); } + var currentVersionPackage = dataModel.getCurrentVersion().getPackage(basePackageName); + // Create the CurrentVersion class { var currentVersionClass = TypeSpec.classBuilder("CurrentVersion"); @@ -306,11 +282,10 @@ public class SourcesGenerator { // Add a static variable for the current version { var versionNumberField = FieldSpec.builder(ClassName - .get(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), + .get(dataModel.getCurrentVersion().getPackage(basePackageName), "Version"), "VERSION").addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC) - .addModifiers(Modifier.FINAL).initializer( - "new " + getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion) - + ".Version()").build(); + .addModifiers(Modifier.FINAL).initializer("new " + dataModel.getCurrentVersion().getPackage(basePackageName) + + ".Version()").build(); currentVersionClass.addField(versionNumberField); } // Check latest version method @@ -327,21 +302,18 @@ public class SourcesGenerator { .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC).returns(ParameterizedTypeName.get( ClassName.get(Set.class), ParameterizedTypeName.get( ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName - .get(joinPackage(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), "data"), + .get(joinPackage(currentVersionPackage, "data"), "IType"))))) .addCode("return $T.of(\n", Set.class); AtomicBoolean isFirst = new AtomicBoolean(true); - for (Entry> entry : configuration.versions.get(configuration.currentVersion).superTypes.entrySet()) { + 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(getVersionPackage(configuration.currentVersion, - basePackageName, - configuration.currentVersion - ), "data"), superTypeName) + ClassName.get(joinPackage(currentVersionPackage, "data"), superTypeName) ); } getSuperTypeClasses.addCode("\n);"); @@ -353,20 +325,17 @@ public class SourcesGenerator { .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC).returns(ParameterizedTypeName.get( ClassName.get(Set.class), ParameterizedTypeName.get( ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName - .get(joinPackage(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), "data"), + .get(joinPackage(currentVersionPackage, "data"), "IBasicType"))))); getSuperTypeSubtypesClasses.addParameter(ParameterSpec.builder(ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName - .get(joinPackage(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), "data"), + .get(joinPackage(currentVersionPackage, "data"), "IType"))), "superTypeClass").build()); getSuperTypeSubtypesClasses.beginControlFlow("switch (superTypeClass.getCanonicalName())"); - for (Entry> entry : configuration.versions.get(configuration.currentVersion).superTypes.entrySet()) { + for (Entry> entry : dataModel.getSuperTypes().entrySet()) { String superTypeName = entry.getKey(); Set subTypes = entry.getValue(); getSuperTypeSubtypesClasses.beginControlFlow("case \"" + ClassName - .get(joinPackage(getVersionPackage(configuration.currentVersion, - basePackageName, - configuration.currentVersion - ), "data"), superTypeName) + .get(joinPackage(currentVersionPackage, "data"), superTypeName) .canonicalName() + "\":"); getSuperTypeSubtypesClasses.addCode("return $T.of(\n", Set.class); AtomicBoolean isFirst = new AtomicBoolean(true); @@ -375,10 +344,7 @@ public class SourcesGenerator { getSuperTypeSubtypesClasses.addCode(",\n"); } getSuperTypeSubtypesClasses.addCode("$T.class", - ClassName.get(joinPackage(getVersionPackage(configuration.currentVersion, - basePackageName, - configuration.currentVersion - ), "data"), subTypeName) + ClassName.get(joinPackage(currentVersionPackage, "data"), subTypeName) ); } getSuperTypeSubtypesClasses.addCode("\n);\n"); @@ -393,7 +359,7 @@ public class SourcesGenerator { // UpgradeDataToLatestVersion1 Method { var upgradeDataToLatestVersion1MethodBuilder = MethodSpec.methodBuilder("upgradeDataToLatestVersion").addTypeVariable(TypeVariableName.get("U", ClassName - .get(joinPackage(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), "data"), + .get(joinPackage(currentVersionPackage, "data"), "IBasicType"))) .addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC).addModifiers(Modifier.FINAL).returns(TypeVariableName.get("U")) .addParameter(ParameterSpec.builder(TypeName.INT, "oldVersion").build()).addParameter( @@ -401,20 +367,16 @@ public class SourcesGenerator { .addParameter(ParameterSpec.builder(DataInput.class, "oldDataInput").build()) .addException(IOException.class).beginControlFlow("switch (oldVersion)"); AtomicInteger seqNumber = new AtomicInteger(0); - for (Entry entry : configuration.versions.entrySet()) { - String version = entry.getKey(); - VersionConfiguration versionConfiguration = entry.getValue(); + for (var versionConfiguration : dataModel.getVersionsSet()) { // Add a case in which the data version deserializes the serialized data and upgrades it - upgradeDataToLatestVersion1MethodBuilder.beginControlFlow("case $T." + getVersionVarName(version) + ":", + 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( - "var deserialized" + seqNumber.incrementAndGet() + " = " + getVersionPackage(configuration.currentVersion, - basePackageName, - version - ) + ".Version.INSTANCE.getSerializer(type).deserialize(oldDataInput)"); - upgradeDataToLatestVersion1MethodBuilder.addStatement( - "return upgradeDataToLatestVersion(Versions." + getVersionVarName(version) + ", deserialized" + "return upgradeDataToLatestVersion(Versions." + versionConfiguration.getVersionVarName() + ", deserialized" + seqNumber.get() + ")"); upgradeDataToLatestVersion1MethodBuilder.endControlFlow(); } @@ -429,7 +391,7 @@ public class SourcesGenerator { 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(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), "data"), + .get(joinPackage(currentVersionPackage, "data"), "IBasicType"))).returns(TypeVariableName.get("U")) .addParameter(ParameterSpec.builder(TypeName.INT, "oldVersion").build()) .addParameter(ParameterSpec.builder(TypeVariableName.get("T"), "oldData").build()) @@ -438,28 +400,26 @@ public class SourcesGenerator { .addStatement("$T intermediateData = oldData", Object.class) .beginControlFlow("while (true)") .beginControlFlow("switch (intermediateVersion)"); - for (Entry entry : configuration.versions.entrySet()) { - String version = entry.getKey(); - VersionConfiguration versionConfiguration = entry.getValue(); + for (var versionConfiguration : dataModel.getVersionsSet()) { // Add a case in which the data version deserializes the serialized data and upgrades it - upgradeDataToLatestVersion2MethodBuilder.beginControlFlow("case $T." + getVersionVarName(version) + ":", + upgradeDataToLatestVersion2MethodBuilder.beginControlFlow("case $T." + versionConfiguration.getVersionVarName() + ":", versionsClassName ); - if (version.equalsIgnoreCase(configuration.currentVersion)) { + if (versionConfiguration.isCurrent()) { // This is the latest version, don't upgrade. upgradeDataToLatestVersion2MethodBuilder.addStatement("return ($T) intermediateData", TypeVariableName.get("U")); } else { // Upgrade upgradeDataToLatestVersion2MethodBuilder .addStatement( - "intermediateData = " + getVersionPackage(configuration.currentVersion, basePackageName, version) + "intermediateData = " + versionConfiguration.getPackage(basePackageName) + ".Version.upgradeToNextVersion(($T) intermediateData)", - ClassName.get(joinPackage(getVersionPackage(configuration.currentVersion, basePackageName, version), + ClassName.get(joinPackage(versionConfiguration.getPackage (basePackageName), "data" ), "IBasicType") ) .addStatement("intermediateVersion = $T." - + getVersionVarName(findNextVersion(configuration, version).orElseThrow()), versionsClassName) + + getVersionVarName(dataModel.getNextVersionOrThrow(versionConfiguration)), versionsClassName) .addStatement("break"); } upgradeDataToLatestVersion2MethodBuilder.endControlFlow(); @@ -475,21 +435,16 @@ public class SourcesGenerator { writeClass(generatedFilesToDelete, outPath, joinPackage(basePackageName, "current"), currentVersionClass); } - for (Entry mapEntry : configuration.versions.entrySet()) { - String version = mapEntry.getKey(); - VersionConfiguration versionConfiguration = mapEntry.getValue(); - var versionPackage = getVersionPackage(configuration.currentVersion, basePackageName, version); + for (var versionConfiguration : dataModel.getVersionsSet()) { + var versionPackage = versionConfiguration.getPackage(basePackageName); var versionClassType = ClassName.get(joinPackage(versionPackage, ""), "Version"); - var nextVersion = findNextVersion(configuration, version); - var nextVersionPackage = nextVersion.map((nextVersionValue) -> getVersionPackage(configuration.currentVersion, - basePackageName, - nextVersionValue - )); + var nextVersion = dataModel.getNextVersion(versionConfiguration); + var nextVersionPackage = nextVersion.map((nextVersionValue) -> nextVersionValue.getPackage(basePackageName)); - logger.info( - "Found version configuration:\n{\n\tversion: \"" + version + "\",\n\tversionPackage: \"" + versionPackage - + "\",\n\tnextVersion: \"" + nextVersion.orElse("unknown") + "\",\n\tnextVersionPackage: \"" - + nextVersionPackage.orElse("unknown") + "\"\n}"); + 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}"); HashMap typeOptionalSerializers = new LinkedHashMap<>(); HashMap typeOptionalUpgraders = new LinkedHashMap<>(); @@ -702,7 +657,7 @@ public class SourcesGenerator { } // Setup only the basic types upgraders variables - for (String s : versionConfiguration.classes.keySet()) { + for (String s : versionConfiguration.getClassMap().keySet()) { if (nextVersion.isPresent()) { typeOptionalUpgraders.put(s, ClassName.get(joinPackage(versionPackage, "upgraders"), s + "Upgrader")); } @@ -710,9 +665,9 @@ public class SourcesGenerator { // Generate the basic and super types Stream - .concat(versionConfiguration.classes.keySet().stream(), versionConfiguration.superTypes.keySet().stream()) + .concat(versionConfiguration.getClassMap().keySet().stream(), dataModel.getSuperTypes().keySet().stream()) .forEach((type) -> { - boolean isBasic = versionConfiguration.classes.containsKey(type); + boolean isBasic = versionConfiguration.getClassMap().containsKey(type); typeOptionalSerializers.put(type, ClassName.get(joinPackage(versionPackage, "serializers"), type + "Serializer") ); @@ -757,11 +712,11 @@ public class SourcesGenerator { }); // Generate the special types - for (Entry entry : versionConfiguration.customTypes.entrySet()) { + for (Entry entry : dataModel.getCustomTypes().entrySet()) { String key = entry.getKey(); CustomTypesConfiguration customTypeConfiguration = entry.getValue(); Optional nextVersionCustomTypeConfiguration = nextVersion - .map(s -> Objects.requireNonNull(configuration.versions.get(s).customTypes.get(key), + .map(s -> Objects.requireNonNull(dataModel.getCustomTypes().get(key), () -> "Custom type " + key + " not found in version " + s )); typeOptionalSerializers.put(key, ClassName.bestGuess(customTypeConfiguration.serializer)); @@ -825,16 +780,16 @@ public class SourcesGenerator { // Check if all types exist { - for (Entry e : versionConfiguration.classes.entrySet()) { + for (Entry e : versionConfiguration.getClassMap().entrySet()) { String type = e.getKey(); - ClassConfiguration typeConfig = e.getValue(); + 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 '" - + version + "'"); + + versionConfiguration.getName() + "'"); } } } @@ -1187,9 +1142,9 @@ public class SourcesGenerator { // Generate the basic types serializers { - for (Entry classConfigurationEntry : versionConfiguration.classes.entrySet()) { + for (Entry classConfigurationEntry : versionConfiguration.getClassMap().entrySet()) { String type = classConfigurationEntry.getKey(); - ClassConfiguration basicTypeConfiguration = classConfigurationEntry.getValue(); + ParsedClass basicTypeConfiguration = classConfigurationEntry.getValue(); var classType = ClassName.get(joinPackage(versionPackage, "data"), type); // Create the basic X serializer class @@ -1313,7 +1268,7 @@ public class SourcesGenerator { } List list = new ArrayList<>(); - for (VersionTransformation versionTransformation : configuration.versions.get(nextVersion.get()).transformations) { + for (VersionTransformation versionTransformation : nextVersion.get().transformations) { if (versionTransformation.isForClass(type)) { list.add(versionTransformation); } @@ -1321,9 +1276,9 @@ public class SourcesGenerator { var transformations = Collections.unmodifiableList(list); AtomicInteger transformationNumber = new AtomicInteger(0); HashMap currentTransformedFieldTypes = new HashMap<>(); - for (Entry stringClassConfigurationEntry : versionConfiguration.classes.entrySet()) { + for (Entry stringClassConfigurationEntry : versionConfiguration.getClassMap().entrySet()) { String className = stringClassConfigurationEntry.getKey(); - ClassConfiguration classConfiguration = stringClassConfigurationEntry.getValue(); + ParsedClass classConfiguration = stringClassConfigurationEntry.getValue(); for (Entry entry : classConfiguration.getData().entrySet()) { String fieldName = entry.getKey(); String fieldType = entry.getValue(); @@ -1400,11 +1355,17 @@ public class SourcesGenerator { TypeName fromType = currentTransformedFieldTypes.get( upgradeDataTransformation.transformClass + "." + upgradeDataTransformation.from); TypeName fromTypeBoxed = fromType.isPrimitive() ? fromType.box() : fromType; - String toTypeName = configuration.versions.get(nextVersion.get()).classes + 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) + "$$" @@ -1454,13 +1415,13 @@ public class SourcesGenerator { } case "new-data" -> { var newDataTransformation = (NewDataConfiguration) transformation; - String newTypeName = configuration.versions.get(nextVersion.get()).classes + 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 " + version + () -> "Type \"" + newTypeName + "\" is not present from next version " + versionConfiguration.getName() + " to version " + nextVersion.get() + " in upgrader " + newDataTransformation.transformClass + "." + newDataTransformation.to ); @@ -1515,7 +1476,7 @@ public class SourcesGenerator { deserializeMethod.addComment( "Upgrade the remaining untouched values to the new version before returning"); - var nextVersionFieldTypes = configuration.versions.get(nextVersion.get()).classes.get(type).getData(); + var nextVersionFieldTypes = nextVersion.get().getClassMap().get(type).getData(); for (var e : currentVarNumber.object2IntEntrySet()) { String key = e.getKey(); int number = e.getIntValue(); @@ -1611,8 +1572,8 @@ public class SourcesGenerator { 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 " + version - + " to version " + nextVersion.orElse("---")); + + currentVarNumber.getInt(field) + ") after upgrading from version " + versionConfiguration.getName() + + " to version " + nextVersion.map(ComputedVersion::getName).orElse("---")); } deserializeMethod.addCode("$$field$$" + currentVarNumber.getInt(field) + "$$" + field); } @@ -1633,7 +1594,7 @@ public class SourcesGenerator { // Generate the super types serializers { - for (Entry> entry : versionConfiguration.superTypes.entrySet()) { + for (Entry> entry : dataModel.getSuperTypes().entrySet()) { String type = entry.getKey(); Set superTypeConfiguration = entry.getValue(); var classType = ClassName.get(joinPackage(versionPackage, "data"), type); @@ -1753,7 +1714,7 @@ public class SourcesGenerator { .addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.STATIC) .addModifiers(Modifier.FINAL) - .initializer("$T." + getVersionVarName(version), + .initializer("$T." + versionConfiguration.getVersionVarName(), ClassName.get(joinPackage(basePackageName, ""), "Versions") ) .build(); @@ -1826,9 +1787,9 @@ public class SourcesGenerator { .builder(ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"), "oldData") .build()) .beginControlFlow("switch (oldData.getBasicType$$()) "); - for (Entry entry : versionConfiguration.classes.entrySet()) { + for (Entry entry : versionConfiguration.getClassMap().entrySet()) { String type = entry.getKey(); - ClassConfiguration typeConfiguration = entry.getValue(); + ParsedClass typeConfiguration = entry.getValue(); var data = typeConfiguration.data; upgradeToNextVersionMethodBuilder.addStatement( "case " + type + ": return $T." + type + "UpgraderInstance.upgrade(($T) oldData)", @@ -1858,9 +1819,9 @@ public class SourcesGenerator { .builder(ClassName.get(joinPackage(basePackageName, ""), "BasicType"), "type") .build()) .beginControlFlow("switch (type)"); - for (Entry entry : versionConfiguration.classes.entrySet()) { + for (Entry entry : versionConfiguration.getClassMap().entrySet()) { String type = entry.getKey(); - ClassConfiguration typeConfiguration = entry.getValue(); + ParsedClass typeConfiguration = entry.getValue(); var data = typeConfiguration.data; getClassMethodBuilder.addStatement("case " + type + ": return $T.class", @@ -1889,9 +1850,9 @@ public class SourcesGenerator { .builder(ClassName.get(joinPackage(basePackageName, ""), "BasicType"), "type") .build()) .beginControlFlow("switch (type)"); - for (Entry entry : versionConfiguration.classes.entrySet()) { + for (Entry entry : versionConfiguration.getClassMap().entrySet()) { String type = entry.getKey(); - ClassConfiguration typeConfiguration = entry.getValue(); + 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")), @@ -2131,7 +2092,7 @@ public class SourcesGenerator { // Create the interfaces { - for (Entry> superType : versionConfiguration.superTypes.entrySet()) { + for (Entry> superType : dataModel.getSuperTypes().entrySet()) { String type = superType.getKey(); Set superTypeConfiguration = superType.getValue(); var iBasicTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"); @@ -2154,7 +2115,7 @@ public class SourcesGenerator { // If it's the latest version, add the common methods if (nextVersion.isEmpty()) { - var interfaceDataConfiguration = configuration.interfacesData.get(type); + var interfaceDataConfiguration = dataModel.getInterfaces().get(type); if (interfaceDataConfiguration != null) { // Extend this interface for (String extendedInterface : interfaceDataConfiguration.extendInterfaces) { @@ -2218,9 +2179,9 @@ public class SourcesGenerator { // Create the basic types classes { - for (Entry stringClassConfigurationEntry : versionConfiguration.classes.entrySet()) { + for (Entry stringClassConfigurationEntry : versionConfiguration.getClassMap().entrySet()) { String type = stringClassConfigurationEntry.getKey(); - ClassConfiguration classConfiguration = stringClassConfigurationEntry.getValue(); + ParsedClass classConfiguration = stringClassConfigurationEntry.getValue(); var typeClass = TypeSpec.recordBuilder(type); typeClass.addModifiers(Modifier.PUBLIC); typeClass.addModifiers(Modifier.STATIC); @@ -2237,7 +2198,7 @@ public class SourcesGenerator { .returns(ClassName.get(joinPackage(basePackageName, ""), "BasicType")) .addStatement("return $T." + type, ClassName.get(joinPackage(basePackageName, ""), "BasicType")); typeClass.addMethod(getBasicTypeMethod.build()); - var superTypes = versionConfiguration.superTypes + var superTypes = dataModel.getSuperTypes() .entrySet() .parallelStream() .filter(((entry) -> entry.getValue().contains(type))) @@ -2271,7 +2232,7 @@ public class SourcesGenerator { if (nextVersion.isEmpty()) { for (Entry superType : superTypes) { if (superType != null) { - var interfaceCommonDataConfiguration = configuration.interfacesData.getOrDefault(superType.getKey(), + var interfaceCommonDataConfiguration = dataModel.getInterfaces().getOrDefault(superType.getKey(), null ); if (interfaceCommonDataConfiguration != null @@ -2396,10 +2357,6 @@ public class SourcesGenerator { generatedFilesToDelete.remove(basePath.relativize(filePath)); } - private String computeHash(SourcesGeneratorConfiguration configuration) { - return Integer.toString(configuration.hashCode()); - } - private TypeName getImmutableArrayType(HashMap typeTypes, String typeString) { var type = typeTypes.get(typeString); return getImmutableArrayType(type); @@ -2694,10 +2651,10 @@ public class SourcesGenerator { boolean nextVersionArrayTypeNeeded){} @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - public NeededTypes registerNeededTypes(VersionConfiguration versionConfiguration, + public NeededTypes registerNeededTypes(ComputedVersion versionConfiguration, Family family, String type, - Optional nextVersion, + Optional nextVersion, Optional nextVersionPackage, ClassName versionClassType, String versionPackage, @@ -2712,10 +2669,10 @@ public class SourcesGenerator { Supplier arrayClassName, Supplier nextArrayClassName) { // Check if the nullable type is needed - boolean nullableTypeNeeded = versionConfiguration.classes + boolean nullableTypeNeeded = versionConfiguration.getClassMap() .values() .parallelStream() - .map(ClassConfiguration::getData) + .map(ParsedClass::getData) .map(Map::values) .flatMap(Collection::parallelStream) .filter((typeZ) -> typeZ.startsWith("-")) @@ -2723,10 +2680,10 @@ public class SourcesGenerator { .anyMatch((typeZ) -> typeZ.equals(type)); boolean nextVersionNullableTypeNeeded = nextVersion - .filter(s -> configuration.versions.get(s).classes + .filter(s -> s.getClassMap() .values() .parallelStream() - .map(ClassConfiguration::getData) + .map(ParsedClass::getData) .map(Map::values) .flatMap(Collection::parallelStream) .filter((typeZ) -> typeZ.startsWith("-")) @@ -2766,20 +2723,20 @@ public class SourcesGenerator { } // Check if the array type is needed - boolean arrayTypeNeeded = versionConfiguration.classes + boolean arrayTypeNeeded = versionConfiguration.getClassMap() .values() .parallelStream() - .map(ClassConfiguration::getData) + .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 -> configuration.versions.get(s).classes + boolean nextVersionArrayTypeNeeded = nextVersion.filter(s -> s.getClassMap() .values() .parallelStream() - .map(ClassConfiguration::getData) + .map(ParsedClass::getData) .map(Map::values) .flatMap(Collection::parallelStream) .filter((typeZ) -> typeZ.startsWith("§")) @@ -2833,17 +2790,6 @@ public class SourcesGenerator { return sb.toString(); } - private String fixType(String fieldType) { - if (fieldType.endsWith("[]") && fieldType.startsWith("-")) { - throw new UnsupportedOperationException("Arrays cannot be null"); - } - if (fieldType.endsWith("[]")) { - return "§" + fieldType.substring(0, fieldType.length() - 2); - } else { - return fieldType; - } - } - private void addImmutableSetter(Builder classBuilder, TypeName classType, Collection fieldNames, String fieldName, TypeName fieldType, boolean isOverride) { var setterMethod = MethodSpec.methodBuilder("set" + capitalize(fieldName)); @@ -2929,36 +2875,9 @@ public class SourcesGenerator { return Character.toUpperCase(field.charAt(0)) + field.substring(1); } - private Optional findNextVersion(SourcesGeneratorConfiguration config, String version) { - int currentVersion = Integer.parseInt(getVersionShortInt(version)); - int maxVersion = Integer.parseInt(getVersionShortInt(config.currentVersion)); - if (currentVersion >= maxVersion) { - return Optional.empty(); - } - - AtomicInteger smallestNextVersion = new AtomicInteger(Integer.MAX_VALUE); - AtomicReference smallestNextVersionString = new AtomicReference<>(null); - for (Entry entry : config.versions.entrySet()) { - String possibleNextVersionString = entry.getKey(); - VersionConfiguration conf = entry.getValue(); - int possibleNextVersion = Integer.parseInt(getVersionShortInt(possibleNextVersionString)); - if (possibleNextVersion <= maxVersion && possibleNextVersion > currentVersion - && possibleNextVersion <= smallestNextVersion.get()) { - smallestNextVersion.set(possibleNextVersion); - smallestNextVersionString.set(possibleNextVersionString); - } - } - - String value = smallestNextVersionString.get(); - return Optional.ofNullable(value); - } - - private String getVersionPackage(String latestVersion, String basePackageName, String version) { - if (latestVersion.equals(version)) { - return joinPackage(basePackageName, "current"); - } else { - return joinPackage(basePackageName, "v" + getVersionCompleteInt(version)); - } + @Deprecated + private String getVersionPackage(String basePackageName, ComputedVersion version) { + return version.getPackage(basePackageName); } private String joinPackage(String basePackageName, String packageName) { @@ -3004,33 +2923,13 @@ public class SourcesGenerator { markFileAsCreated(generatedFilesToDelete, outPath, outJavaFile); } - private String getVersionVarName(String version) { - return "V" + version.replace('-', '_').replace('.', '_'); + @Deprecated + private String getVersionVarName(ComputedVersion computedVersion) { + return computedVersion.getVersionVarName(); } - private String getVersionCompleteInt(String version) { - String[] parts = version.split("\\."); - while (parts[0].length() < 2) { - parts[0] = "0" + parts[0]; - } - while (parts[1].length() < 3) { - parts[1] = "0" + parts[1]; - } - while (parts[2].length() < 4) { - parts[2] = "0" + parts[2]; - } - return parts[0] + parts[1] + parts[2]; - } - - private String getVersionShortInt(String version) { - version = getVersionCompleteInt(version); - while (version.startsWith("0")) { - version = version.substring(1); - } - if (version.isBlank()) { - return "0"; - } - return version; + private String getVersionShortInt(ComputedVersion version) { + return Integer.toString(version.getVersion()); } private enum Family { diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGeneratorConfiguration.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGeneratorConfiguration.java index ba7e669..e4560fc 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGeneratorConfiguration.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGeneratorConfiguration.java @@ -2,12 +2,15 @@ package it.cavallium.data.generator; import java.util.Map; import java.util.Objects; +import java.util.Set; public class SourcesGeneratorConfiguration { public String currentVersion; public Map interfacesData; + public Map baseTypesData; + public Map> superTypesData; + public Map customTypesData; public Map versions; - public SourcesGeneratorConfigurationRefs refs; @Override public boolean equals(Object o) { @@ -19,7 +22,8 @@ public class SourcesGeneratorConfiguration { } SourcesGeneratorConfiguration that = (SourcesGeneratorConfiguration) o; return Objects.equals(currentVersion, that.currentVersion) && Objects.equals(interfacesData, that.interfacesData) - && Objects.equals(versions, that.versions) && Objects.equals(refs, that.refs); + && Objects.equals(baseTypesData, that.baseTypesData) && Objects.equals(superTypesData, that.superTypesData) + && Objects.equals(customTypesData, that.customTypesData) && Objects.equals(versions, that.versions); } @Override @@ -27,8 +31,20 @@ public class SourcesGeneratorConfiguration { int hash = 0; hash += ConfigUtils.hashCode(currentVersion); hash += ConfigUtils.hashCode(interfacesData); + hash += ConfigUtils.hashCode(superTypesData); + hash += ConfigUtils.hashCode(customTypesData); hash += ConfigUtils.hashCode(versions); - hash += ConfigUtils.hashCode(refs); return hash; } + + public DataModel buildDataModel() { + return new DataModel(hashCode(), + currentVersion, + Objects.requireNonNullElse(interfacesData, Map.of()), + Objects.requireNonNullElse(baseTypesData, Map.of()), + Objects.requireNonNullElse(superTypesData, Map.of()), + Objects.requireNonNullElse(customTypesData, Map.of()), + Objects.requireNonNullElse(versions, Map.of()) + ); + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGeneratorConfigurationRefs.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGeneratorConfigurationRefs.java deleted file mode 100644 index 0123863..0000000 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/SourcesGeneratorConfigurationRefs.java +++ /dev/null @@ -1,36 +0,0 @@ -package it.cavallium.data.generator; - -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -public class SourcesGeneratorConfigurationRefs { - public Map>> superTypes; - public Map> customTypes; - public Map> classes; - public Map> transformations; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - SourcesGeneratorConfigurationRefs that = (SourcesGeneratorConfigurationRefs) o; - return Objects.equals(superTypes, that.superTypes) && Objects.equals(customTypes, that.customTypes) - && Objects.equals(classes, that.classes) && Objects.equals(transformations, that.transformations); - } - - @Override - public int hashCode() { - int hash = 0; - hash += ConfigUtils.hashCode(superTypes); - hash += ConfigUtils.hashCode(customTypes); - hash += ConfigUtils.hashCode(classes); - hash += ConfigUtils.hashCode(transformations); - return hash; - } -} diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/UpgradeDataConfiguration.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/UpgradeDataConfiguration.java index 9878379..ab282ed 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/UpgradeDataConfiguration.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/UpgradeDataConfiguration.java @@ -6,6 +6,7 @@ public class UpgradeDataConfiguration implements TransformationConfiguration { public String transformClass; public String from; + public String type; public String upgrader; @Override @@ -27,10 +28,8 @@ public class UpgradeDataConfiguration implements TransformationConfiguration { return false; } UpgradeDataConfiguration that = (UpgradeDataConfiguration) o; - return Objects.equals(transformClass, that.transformClass) && Objects.equals(from, that.from) && Objects.equals( - upgrader, - that.upgrader - ); + return Objects.equals(transformClass, that.transformClass) && Objects.equals(from, that.from) + && Objects.equals(type, that.type) && Objects.equals(upgrader, that.upgrader); } @Override @@ -38,7 +37,19 @@ public class UpgradeDataConfiguration implements TransformationConfiguration { int hash = 0; hash += ConfigUtils.hashCode(transformClass); hash += ConfigUtils.hashCode(from); + hash += ConfigUtils.hashCode(type); hash += ConfigUtils.hashCode(upgrader); return hash; } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public UpgradeDataConfiguration clone() { + var c = new UpgradeDataConfiguration(); + if (this.transformClass != null) c.transformClass = this.transformClass; + if (this.from != null) c.from = this.from; + if (this.type != null) c.type = this.type; + if (this.upgrader != null) c.upgrader = this.upgrader; + return c; + } } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/VersionConfiguration.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/VersionConfiguration.java index f76cc7c..bba6999 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/VersionConfiguration.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/VersionConfiguration.java @@ -7,10 +7,8 @@ import java.util.Set; public class VersionConfiguration { + public String previousVersion; public DetailsConfiguration details; - public Map> superTypes; - public Map customTypes; - public Map classes; public List transformations; @Override @@ -22,19 +20,15 @@ public class VersionConfiguration { return false; } VersionConfiguration that = (VersionConfiguration) o; - return Objects.equals(details, that.details) && Objects.equals(superTypes, that.superTypes) && Objects.equals( - customTypes, - that.customTypes - ) && Objects.equals(classes, that.classes) && Objects.equals(transformations, that.transformations); + return Objects.equals(previousVersion, that.previousVersion) && Objects.equals(details, that.details) + && Objects.equals(transformations, that.transformations); } @Override public int hashCode() { int hash = 0; + hash += ConfigUtils.hashCode(previousVersion); hash += ConfigUtils.hashCode(details); - hash += ConfigUtils.hashCode(superTypes); - hash += ConfigUtils.hashCode(customTypes); - hash += ConfigUtils.hashCode(classes); hash += ConfigUtils.hashCode(transformations); return hash; } diff --git a/data-generator-plugin/src/main/java/it/cavallium/data/generator/VersionTransformation.java b/data-generator-plugin/src/main/java/it/cavallium/data/generator/VersionTransformation.java index 541a534..6e40f14 100644 --- a/data-generator-plugin/src/main/java/it/cavallium/data/generator/VersionTransformation.java +++ b/data-generator-plugin/src/main/java/it/cavallium/data/generator/VersionTransformation.java @@ -2,7 +2,7 @@ package it.cavallium.data.generator; import java.util.Objects; -public class VersionTransformation { +public final class VersionTransformation { public MoveDataConfiguration moveData = null; public RemoveDataConfiguration removeData = null; @@ -86,4 +86,15 @@ public class VersionTransformation { hash += ConfigUtils.hashCode(newData); return hash; } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public VersionTransformation clone() { + var t = new VersionTransformation(); + if (this.moveData != null) t.moveData = this.moveData.clone(); + if (this.removeData != null) t.removeData = this.removeData.clone(); + if (this.upgradeData != null) t.upgradeData = this.upgradeData.clone(); + if (this.newData != null) t.newData = this.newData.clone(); + return t; + } } diff --git a/data-generator-plugin/src/test/java/it/cavallium/data/generator/TestGenerator.java b/data-generator-plugin/src/test/java/it/cavallium/data/generator/TestGenerator.java new file mode 100644 index 0000000..e8ef290 --- /dev/null +++ b/data-generator-plugin/src/test/java/it/cavallium/data/generator/TestGenerator.java @@ -0,0 +1,23 @@ +package it.cavallium.data.generator; + +import java.io.IOException; +import java.nio.file.Files; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Test; + +public class TestGenerator { + + //@Test + 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); + } finally { + try { + FileUtils.deleteDirectory(dir.toFile()); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/data-generator-plugin/src/test/resources/test.yaml b/data-generator-plugin/src/test/resources/test.yaml new file mode 100644 index 0000000..e69de29