diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml new file mode 100644 index 0000000..28d1033 --- /dev/null +++ b/.github/workflows/maven-publish.yml @@ -0,0 +1,50 @@ +# This workflow will build a package using Maven and then publish it to GitHub packages when a release is created +# For more information see: https://github.com/actions/setup-java#apache-maven-with-a-settings-path + +name: Maven Package + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * 0' # weekly + +jobs: + build: + strategy: + matrix: + include: + - { os: ubuntu-20.04, arch: "linux/amd64" } + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + with: + submodules: "recursive" + - name: Setup variables + shell: bash + run: | + # ====== Variables + export REVISION=${{ github.run_number }} + + echo "REVISION=$REVISION" >> $GITHUB_ENV + - name: Set up JDK 15 + if: github.ref == 'refs/heads/master' + uses: actions/setup-java@v1 + with: + java-version: 15 + server-id: mchv-release-distribution + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + env: + MAVEN_USERNAME: ${{ secrets.MCHV_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MCHV_TOKEN }} + - name: Deploy to Maven (Release) + if: github.ref == 'refs/heads/master' + shell: bash + run: | + echo "REVISION: $REVISION" + + mvn -B -Drevision=${REVISION} clean deploy + env: + MAVEN_USERNAME: ${{ secrets.MCHV_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MCHV_TOKEN }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..bfdf6b2 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +Data generator +============== +Maven plugin to generate data classes from a .yaml definition file. + +It can be also executed standalone. + +The data is serializable and upgradable from any version to the latest. + +The transformations between each version are defined in the .yaml file itself. + +Supports custom (external) data types, custom data arrays, custom data optionals, interfaces with common getters / setters. + +The serialized data is very lightweight: it serializes only the data, without any metadata or type specification, because it's all deducted on compile-time from the definitions file. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e240a7f --- /dev/null +++ b/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + Data generator + it.cavallium + data-generator + 0.9.0-SNAPSHOT + maven-plugin + + 11 + 11 + UTF-8 + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.6.0 + + + + + + + commons-io + commons-io + 2.8.0 + + + org.yaml + snakeyaml + 1.26 + + + com.squareup + javapoet + 1.13.0 + + + org.jetbrains + annotations + 19.0.0 + + + it.unimi.dsi + fastutil + 8.3.1 + + + org.apache.maven + maven-core + 3.6.0 + provided + + + org.apache.maven + maven-plugin-api + 3.6.3 + provided + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.6.0 + provided + + + org.apache.maven + maven-project + 2.2.1 + + + junit + junit + + + + + org.projectlombok + lombok + 1.18.18 + + + org.slf4j + slf4j-api + 1.7.30 + + + org.apache.logging.log4j + log4j-core + 2.12.1 + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.12.1 + + + \ No newline at end of file diff --git a/src/main/java/it/cavallium/data/generator/CachedReflection.java b/src/main/java/it/cavallium/data/generator/CachedReflection.java new file mode 100644 index 0000000..c5f8e7a --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/CachedReflection.java @@ -0,0 +1,50 @@ +package it.cavallium.data.generator; + +import java.lang.reflect.Method; +import java.util.NoSuchElementException; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +public class CachedReflection { + private static ConcurrentHashMap> classes = new ConcurrentHashMap<>(); + private static ConcurrentHashMap methods = new ConcurrentHashMap<>(); + + public static Class classForName(String str) throws ClassNotFoundException { + try { + return classes.computeIfAbsent(str, (x) -> { + try { + return Class.forName(str); + } catch (ClassNotFoundException e) { + throw new CompletionException(e); + } + }); + } catch (CompletionException ex) { + var cause = ex.getCause(); + if (cause instanceof ClassNotFoundException) { + throw (ClassNotFoundException) cause; + } + throw ex; + } + } + + public static Method getDeclaredMethod(Class type, String name) throws NoSuchElementException, SecurityException { + try { + return methods.computeIfAbsent(type + "$$$" + name, (x) -> { + try { + return Stream.of(type.getDeclaredMethods()).filter(method -> method.getName().equals(name)).findAny().get(); + } catch (NoSuchElementException | SecurityException e) { + throw new CompletionException(e); + } + }); + } catch (CompletionException ex) { + var cause = ex.getCause(); + if (cause instanceof NoSuchElementException) { + throw (NoSuchElementException) cause; + } else if (cause instanceof SecurityException) { + throw (SecurityException) cause; + } + throw ex; + } + } +} diff --git a/src/main/java/it/cavallium/data/generator/CommonField.java b/src/main/java/it/cavallium/data/generator/CommonField.java new file mode 100644 index 0000000..9b6b1fa --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/CommonField.java @@ -0,0 +1,13 @@ +package it.cavallium.data.generator; + +public class CommonField { + public final String fieldName; + public final String fieldType; + public final boolean hasSetter; + + public CommonField(String fieldName, String fieldType, boolean hasSetter) { + this.fieldName = fieldName; + this.fieldType = fieldType; + this.hasSetter = hasSetter; + } +} diff --git a/src/main/java/it/cavallium/data/generator/DataInitializer.java b/src/main/java/it/cavallium/data/generator/DataInitializer.java new file mode 100644 index 0000000..493853d --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/DataInitializer.java @@ -0,0 +1,9 @@ +package it.cavallium.data.generator; + +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public interface DataInitializer { + + @NotNull T initialize() throws IOException; +} diff --git a/src/main/java/it/cavallium/data/generator/DataInput3.java b/src/main/java/it/cavallium/data/generator/DataInput3.java new file mode 100644 index 0000000..b7672d4 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/DataInput3.java @@ -0,0 +1,23 @@ +package it.cavallium.data.generator; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; + +public class DataInput3 extends DataInputStream { + + private final byte[] buffer; + + /** + * Creates a DataInputStream that uses the specified underlying InputStream. + * + * @param in the specified input stream + */ + public DataInput3(byte[] in) { + super(new ByteArrayInputStream(in)); + this.buffer = in; + } + + public byte[] asBytes() { + return buffer; + } +} diff --git a/src/main/java/it/cavallium/data/generator/DataSerializer.java b/src/main/java/it/cavallium/data/generator/DataSerializer.java new file mode 100644 index 0000000..7ed43e0 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/DataSerializer.java @@ -0,0 +1,13 @@ +package it.cavallium.data.generator; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public interface DataSerializer { + + void serialize(DataOutput dataOutput, @NotNull T data) throws IOException; + + @NotNull T deserialize(DataInput dataInput) throws IOException; +} diff --git a/src/main/java/it/cavallium/data/generator/DataUpgrader.java b/src/main/java/it/cavallium/data/generator/DataUpgrader.java new file mode 100644 index 0000000..d6e3f61 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/DataUpgrader.java @@ -0,0 +1,9 @@ +package it.cavallium.data.generator; + +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public interface DataUpgrader { + + @NotNull U upgrade(@NotNull T data) throws IOException; +} diff --git a/src/main/java/it/cavallium/data/generator/MavenPlugin.java b/src/main/java/it/cavallium/data/generator/MavenPlugin.java new file mode 100644 index 0000000..a9f86b7 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/MavenPlugin.java @@ -0,0 +1,47 @@ +package it.cavallium.data.generator; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import org.apache.commons.io.FileUtils; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; + +@Mojo(name = "run", defaultPhase = LifecyclePhase.GENERATE_SOURCES) +public class MavenPlugin extends AbstractMojo { + + @Parameter( required = true) + private File configPath; + + @Parameter( required = true) + private String basePackageName; + + /** + * @parameter default-value="${project}" + * @required + * @readonly + */ + @Parameter(defaultValue = "${project}", required = true, readonly = false) + MavenProject project; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + try { + SourcesGenerator sourcesGenerator = SourcesGenerator.load(configPath.toPath()); + Path genRecordsPath = project.getBasedir().getAbsoluteFile().toPath().resolve("target").resolve("generated-sources").resolve("database-classes"); + FileUtils.deleteDirectory(genRecordsPath.resolve(Path.of(basePackageName.replace('.', File.separatorChar))).toFile()); + + Path outPath = genRecordsPath.resolve("java"); + this.project.addCompileSourceRoot(outPath.toString()); + sourcesGenerator.generateSources(basePackageName, outPath); + } catch (IOException e) { + throw new MojoExecutionException("Exception while generating classes", e); + } + getLog().info("Classes generated."); + } +} diff --git a/src/main/java/it/cavallium/data/generator/Nullable.java b/src/main/java/it/cavallium/data/generator/Nullable.java new file mode 100644 index 0000000..e153dd6 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/Nullable.java @@ -0,0 +1,50 @@ +package it.cavallium.data.generator; + +public class Nullable { + + private static final long serialVersionUID = 1L; + + private final T value; + + public Nullable(T value) { + this.value = value; + } + + public static Nullable of(T value) { + if (value == null) { + throw new NullPointerException(); + } else { + return new Nullable<>(value); + } + } + + public static Nullable ofNullable(T value) { + return new Nullable<>(value); + } + + public static Nullable empty() { + return new Nullable<>(null); + } + + public boolean isEmpty() { + return value == null; + } + + public boolean isPresent() { + return value != null; + } + + @org.jetbrains.annotations.NotNull + public T get() { + if (value == null) { + throw new NullPointerException(); + } else { + return value; + } + } + + @org.jetbrains.annotations.Nullable + public T getNullable() { + return value; + } +} diff --git a/src/main/java/it/cavallium/data/generator/SerializeCodeBlockGenerator.java b/src/main/java/it/cavallium/data/generator/SerializeCodeBlockGenerator.java new file mode 100644 index 0000000..58c3417 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/SerializeCodeBlockGenerator.java @@ -0,0 +1,47 @@ +package it.cavallium.data.generator; + +import com.squareup.javapoet.CodeBlock; +import java.util.List; +import java.util.Objects; + +public class SerializeCodeBlockGenerator { + + private final CodeBlock before; + private final CodeBlock after; + + public SerializeCodeBlockGenerator(CodeBlock before, CodeBlock after) { + this.before = before; + this.after = after; + } + + public CodeBlock generate(String method) { + return generate(CodeBlock.builder().add(method).build()); + } + + public CodeBlock generate(CodeBlock method) { + return CodeBlock.join(List.of(before, method, after), ""); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SerializeCodeBlockGenerator that = (SerializeCodeBlockGenerator) o; + return Objects.equals(before, that.before) && Objects.equals(after, that.after); + } + + @Override + public int hashCode() { + return Objects.hash(before, after); + } + + + @Override + public String toString() { + return CodeBlock.builder().add("$[").add(generate(CodeBlock.builder().add("test.get()").build())).add(";\n$]").build().toString(); + } +} diff --git a/src/main/java/it/cavallium/data/generator/SourcesGenerator.java b/src/main/java/it/cavallium/data/generator/SourcesGenerator.java new file mode 100644 index 0000000..9be4a2b --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/SourcesGenerator.java @@ -0,0 +1,2308 @@ +package it.cavallium.data.generator; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeSpec.Builder; +import com.squareup.javapoet.TypeVariableName; +import com.squareup.javapoet.WildcardTypeName; +import it.cavallium.data.generator.nativedata.IGenericNullable; +import it.cavallium.data.generator.nativedata.StringSerializer; +import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.ToString; +import lombok.Value; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; +import it.cavallium.data.generator.SourcesGeneratorConfiguration.ClassConfiguration; +import it.cavallium.data.generator.SourcesGeneratorConfiguration.CustomTypesConfiguration; +import it.cavallium.data.generator.SourcesGeneratorConfiguration.InterfaceDataConfiguration; +import it.cavallium.data.generator.SourcesGeneratorConfiguration.MoveDataConfiguration; +import it.cavallium.data.generator.SourcesGeneratorConfiguration.NewDataConfiguration; +import it.cavallium.data.generator.SourcesGeneratorConfiguration.RemoveDataConfiguration; +import it.cavallium.data.generator.SourcesGeneratorConfiguration.UpgradeDataConfiguration; +import it.cavallium.data.generator.SourcesGeneratorConfiguration.VersionConfiguration; +import it.cavallium.data.generator.SourcesGeneratorConfiguration.VersionTransformation; + +@SuppressWarnings({"SameParameterValue", "unused"}) +public class SourcesGenerator { + + private static final Logger logger = LoggerFactory.getLogger(SourcesGenerator.class); + + private final SourcesGeneratorConfiguration configuration; + + private SourcesGenerator(InputStream yamlDataStream) { + Yaml yaml = new Yaml(); + this.configuration = yaml.loadAs(yamlDataStream, SourcesGeneratorConfiguration.class); + } + + public static SourcesGenerator load(InputStream yamlData) { + return new SourcesGenerator(yamlData); + } + + public static SourcesGenerator load(Path yamlPath) throws IOException { + try (InputStream in = Files.newInputStream(yamlPath)) { + return new SourcesGenerator(in); + } + } + + public static SourcesGenerator load(File yamlPath) throws IOException { + try (InputStream in = Files.newInputStream(yamlPath.toPath())) { + return new SourcesGenerator(in); + } + } + + /** + * @param basePackageName org.example + * @param outPath path/to/output + */ + public void generateSources(String basePackageName, Path outPath) throws IOException { + + // 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); + versionsClass.addModifiers(Modifier.FINAL); + var versionsInstances = FieldSpec.builder(ArrayTypeName.of(ClassName.get(joinPackage(basePackageName, ""), "IVersion")), + "VERSIONS", + Modifier.PUBLIC, + Modifier.STATIC, + Modifier.FINAL + ); + List versionsInstancesValue = new ArrayList<>(); + for (Entry stringVersionConfigurationEntry : configuration.versions.entrySet()) { + String version = stringVersionConfigurationEntry.getKey(); + VersionConfiguration value = stringVersionConfigurationEntry.getValue(); + // Add a static variable for this version, containing the normalized version number + var versionNumberField = FieldSpec + .builder(TypeName.INT, getVersionVarName(version)) + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.STATIC) + .addModifiers(Modifier.FINAL) + .initializer(getVersionShortInt(version)) + .build(); + // Add the fields to the class + versionsClass.addField(versionNumberField); + + var versionPackage = getVersionPackage(configuration.currentVersion, basePackageName, version); + var versionClassType = ClassName.get(joinPackage(versionPackage, ""), "Version"); + + versionsInstancesValue.add(CodeBlock.builder().add("$T.INSTANCE", versionClassType).build()); + } + versionsInstances.initializer(CodeBlock + .builder() + .add("{\n") + .add(CodeBlock.join(versionsInstancesValue, ",\n")) + .add("\n}") + .build()); + versionsClass.addField(versionsInstances.build()); + // Save the resulting class in the main package + writeClass(outPath, joinPackage(basePackageName, ""), versionsClass); + + // Create the BasicType class + { + 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()) { + if (!basicTypeClass.enumConstants.containsKey(basicTypeName)) { + basicTypeClass.addEnumConstant(basicTypeName); + } + } + } + // Save the resulting class in the main package + writeClass(outPath, joinPackage(basePackageName, ""), basicTypeClass); + } + + // Create the IVersion class + { + var iVersionClass = TypeSpec.interfaceBuilder("IVersion"); + iVersionClass.addModifiers(Modifier.PUBLIC); + iVersionClass.addTypeVariable(TypeVariableName.get("B")); + + // Add getClass method + { + var getClassMethodBuilder = MethodSpec + .methodBuilder("getClass") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .returns(ParameterizedTypeName.get(ClassName.get(Class.class), + WildcardTypeName.subtypeOf(TypeVariableName.get("B")) + )) + .addParameter(ParameterSpec + .builder(ClassName.get(joinPackage(basePackageName, ""), "BasicType"), "type") + .build()); + iVersionClass.addMethod(getClassMethodBuilder.build()); + } + + // Add getSerializer method + { + var getSerializerMethodBuilder = MethodSpec + .methodBuilder("getSerializer") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .addTypeVariable(TypeVariableName.get("T", + TypeVariableName.get("B") + )) + .returns(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), TypeVariableName.get("T"))) + .addException(IOException.class) + .addParameter(ParameterSpec + .builder(ClassName.get(joinPackage(basePackageName, ""), "BasicType"), "type") + .build()); + iVersionClass.addMethod(getSerializerMethodBuilder.build()); + } + + // Add getVersion method + { + var getVersionMethod = MethodSpec + .methodBuilder("getVersion") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .returns(TypeName.INT) + .build(); + iVersionClass.addMethod(getVersionMethod); + } + + // Save the resulting class in the main package + writeClass(outPath, joinPackage(basePackageName, ""), iVersionClass); + } + + // Create the CurrentVersion class + { + var currentVersionClass = TypeSpec.classBuilder("CurrentVersion"); + currentVersionClass.addModifiers(Modifier.PUBLIC); + currentVersionClass.addModifiers(Modifier.FINAL); + // Add a static variable for the current version + { + var versionNumberField = FieldSpec.builder(ClassName + .get(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), + "Version"), "VERSION").addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC) + .addModifiers(Modifier.FINAL).initializer( + "new " + getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion) + + ".Version()").build(); + currentVersionClass.addField(versionNumberField); + } + // Check latest version method + { + var isLatestVersionMethod = MethodSpec.methodBuilder("isLatestVersion").addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC).returns(TypeName.BOOLEAN) + .addParameter(ParameterSpec.builder(TypeName.INT, "version").build()) + .addCode("return version == VERSION.getVersion();").build(); + currentVersionClass.addMethod(isLatestVersionMethod); + } + // Get super type classes method + { + var getSuperTypeClasses = MethodSpec.methodBuilder("getSuperTypeClasses").addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC).returns(ParameterizedTypeName.get( + ClassName.get(Set.class), ParameterizedTypeName.get( + ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName + .get(joinPackage(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), "data"), + "IType"))))) + .addCode("return $T.of(\n", Set.class); + AtomicBoolean isFirst = new AtomicBoolean(true); + for (Entry> entry : configuration.versions.get(configuration.currentVersion).superTypes.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) + ); + } + getSuperTypeClasses.addCode("\n);"); + currentVersionClass.addMethod(getSuperTypeClasses.build()); + } + // Get super type subtypes classes method + { + var getSuperTypeSubtypesClasses = MethodSpec.methodBuilder("getSuperTypeSubtypesClasses").addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL).addModifiers(Modifier.STATIC).returns(ParameterizedTypeName.get( + ClassName.get(Set.class), ParameterizedTypeName.get( + ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName + .get(joinPackage(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), "data"), + "IBasicType"))))); + getSuperTypeSubtypesClasses.addParameter(ParameterSpec.builder(ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName + .get(joinPackage(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), "data"), + "IType"))), "superTypeClass").build()); + getSuperTypeSubtypesClasses.beginControlFlow("switch (superTypeClass.getCanonicalName())"); + for (Entry> entry : configuration.versions.get(configuration.currentVersion).superTypes.entrySet()) { + String superTypeName = entry.getKey(); + Set subTypes = entry.getValue(); + getSuperTypeSubtypesClasses.beginControlFlow("case \"" + ClassName + .get(joinPackage(getVersionPackage(configuration.currentVersion, + basePackageName, + configuration.currentVersion + ), "data"), superTypeName) + .canonicalName() + "\":"); + getSuperTypeSubtypesClasses.addCode("return $T.of(\n", Set.class); + AtomicBoolean isFirst = new AtomicBoolean(true); + for (String subTypeName : subTypes) { + if (!isFirst.getAndSet(false)) { + getSuperTypeSubtypesClasses.addCode(",\n"); + } + getSuperTypeSubtypesClasses.addCode("$T.class", + ClassName.get(joinPackage(getVersionPackage(configuration.currentVersion, + basePackageName, + configuration.currentVersion + ), "data"), subTypeName) + ); + } + getSuperTypeSubtypesClasses.addCode("\n);\n"); + getSuperTypeSubtypesClasses.endControlFlow(); + } + getSuperTypeSubtypesClasses.beginControlFlow("default:"); + getSuperTypeSubtypesClasses.addStatement("throw new $T()", IllegalArgumentException.class); + getSuperTypeSubtypesClasses.endControlFlow(); + getSuperTypeSubtypesClasses.endControlFlow(); + currentVersionClass.addMethod(getSuperTypeSubtypesClasses.build()); + } + // UpgradeDataToLatestVersion1 Method + { + var upgradeDataToLatestVersion1MethodBuilder = MethodSpec.methodBuilder("upgradeDataToLatestVersion").addTypeVariable(TypeVariableName.get("U", ClassName + .get(joinPackage(getVersionPackage(configuration.currentVersion, basePackageName, configuration.currentVersion), "data"), + "IBasicType"))) + .addModifiers(Modifier.PUBLIC).addModifiers(Modifier.STATIC).addModifiers(Modifier.FINAL).returns(TypeVariableName.get("U")) + .addParameter(ParameterSpec.builder(TypeName.INT, "oldVersion").build()).addParameter( + ParameterSpec.builder(ClassName.get(joinPackage(basePackageName, ""), "BasicType"), "type").build()) + .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(); +// Add a case in which the data version deserializes the serialized data and upgrades it + upgradeDataToLatestVersion1MethodBuilder.beginControlFlow("case $T." + getVersionVarName(version) + ":", + ClassName.get(joinPackage(basePackageName, ""), "Versions") + ); + 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" + + seqNumber.get() + ")"); + upgradeDataToLatestVersion1MethodBuilder.endControlFlow(); + } + var upgradeDataToLatestVersion1Method = upgradeDataToLatestVersion1MethodBuilder.beginControlFlow("default:") + .addStatement("throw new $T(\"Unknown version: \" + oldVersion)", IOException.class).endControlFlow() + .endControlFlow().build(); + currentVersionClass.addMethod(upgradeDataToLatestVersion1Method); + } + // UpgradeDataToLatestVersion2 Method + { + var 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"), + "IBasicType"))).returns(TypeVariableName.get("U")) + .addParameter(ParameterSpec.builder(TypeName.INT, "oldVersion").build()) + .addParameter(ParameterSpec.builder(TypeVariableName.get("T"), "oldData").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(); +// Add a case in which the data version deserializes the serialized data and upgrades it + upgradeDataToLatestVersion2MethodBuilder.beginControlFlow("case $T." + getVersionVarName(version) + ":", + ClassName.get(joinPackage(basePackageName, ""), "Versions") + ); + if (version.equalsIgnoreCase(configuration.currentVersion)) { + // This is the latest version, don't upgrade. + upgradeDataToLatestVersion2MethodBuilder.addStatement("return ($T) oldData", TypeVariableName.get("U")); + } else { + // Upgrade + upgradeDataToLatestVersion2MethodBuilder.addStatement( + "var upgradedData" + seqNumber.incrementAndGet() + " = " + + getVersionPackage(configuration.currentVersion, basePackageName, version) + + ".Version.upgradeToNextVersion(($T) oldData)", + ClassName.get(joinPackage(getVersionPackage(configuration.currentVersion, basePackageName, version), + "data" + ), "IBasicType") + ); + upgradeDataToLatestVersion2MethodBuilder.addStatement( + "return upgradeDataToLatestVersion(Versions." + getVersionVarName(findNextVersion(configuration, + version + ).orElseThrow()) + ", upgradedData" + seqNumber.get() + ")"); + } + upgradeDataToLatestVersion2MethodBuilder.endControlFlow(); + } + var upgradeDataToLatestVersion2Method = upgradeDataToLatestVersion2MethodBuilder.beginControlFlow("default:") + .addStatement("throw new $T(\"Unknown version: \" + oldVersion)", IOException.class).endControlFlow() + .endControlFlow().build(); + currentVersionClass.addMethod(upgradeDataToLatestVersion2Method); + } + // Save the resulting class in the main package + writeClass(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); + var versionClassType = ClassName.get(joinPackage(versionPackage, ""), "Version"); + var nextVersion = findNextVersion(configuration, version); + var nextVersionPackage = nextVersion.map((nextVersionValue) -> getVersionPackage(configuration.currentVersion, + basePackageName, + nextVersionValue + )); + + logger.info( + "Found version configuration:\n{\n\tversion: \"" + version + "\",\n\tversionPackage: \"" + versionPackage + + "\",\n\tnextVersion: \"" + nextVersion.orElse("unknown") + "\",\n\tnextVersionPackage: \"" + + nextVersionPackage.orElse("unknown") + "\"\n}"); + + HashMap typeOptionalSerializers = new LinkedHashMap<>(); + HashMap typeOptionalUpgraders = new LinkedHashMap<>(); + HashMap typeSerializeStatement = new HashMap<>(); + HashMap typeDeserializeStatement = new HashMap<>(); + HashMap typeMustGenerateSerializer = new HashMap<>(); + HashMap typeTypes = new LinkedHashMap<>(Map.of("boolean", + TypeName.BOOLEAN, + "short", + TypeName.SHORT, + "char", + TypeName.CHAR, + "int", + TypeName.INT, + "long", + TypeName.LONG, + "float", + TypeName.FLOAT, + "double", + TypeName.DOUBLE, + "byte", + TypeName.BYTE + )); + @Nullable HashMap nextVersionTypeTypes; + if (nextVersion.isPresent()) { + nextVersionTypeTypes = new LinkedHashMap<>(Map.of("boolean", + TypeName.BOOLEAN, + "short", + TypeName.SHORT, + "char", + TypeName.CHAR, + "int", + TypeName.INT, + "long", + TypeName.LONG, + "float", + TypeName.FLOAT, + "double", + TypeName.DOUBLE, + "byte", + TypeName.BYTE + )); + } else { + nextVersionTypeTypes = null; + } + Set specialNativeTypes = Set.of("String", + "boolean", + "short", + "char", + "int", + "long", + "float", + "double", + "byte" + ); + + // Generate the type statements + { + // Generate the native types + for (String specialNativeType : specialNativeTypes) { + if (Character.isUpperCase(specialNativeType.charAt(0))) { + typeTypes.put(specialNativeType, ClassName.get("java.lang", specialNativeType)); + if (nextVersion.isPresent()) { + nextVersionTypeTypes.put(specialNativeType, ClassName.get("java.lang", specialNativeType)); + } + if (specialNativeType.equals("String")) { + typeSerializeStatement.put(specialNativeType, + new SerializeCodeBlockGenerator(CodeBlock + .builder() + .add("$T.INSTANCE.serialize(dataOutput, ", StringSerializer.class) + .build(), CodeBlock.builder().add(")").build()) + ); + typeDeserializeStatement.put(specialNativeType, + CodeBlock.builder().add("$T.INSTANCE.deserialize(dataInput)", StringSerializer.class).build() + ); + } else { + typeSerializeStatement.put(specialNativeType, + new SerializeCodeBlockGenerator(CodeBlock + .builder() + .add("dataOutput.write" + specialNativeType + "(") + .build(), CodeBlock.builder().add(")").build()) + ); + typeDeserializeStatement.put(specialNativeType, + CodeBlock.builder().add("dataInput.read" + specialNativeType + "()").build() + ); + } + } else { + var uppercasedType = Character.toUpperCase(specialNativeType.charAt(0)) + specialNativeType.substring(1); + // don't put the special type, because it's already in the hashmap. + typeSerializeStatement.put(specialNativeType, + new SerializeCodeBlockGenerator(CodeBlock + .builder() + .add("dataOutput.write" + uppercasedType + "(") + .build(), CodeBlock.builder().add(")").build()) + ); + typeDeserializeStatement.put(specialNativeType, + CodeBlock.builder().add("dataInput.read" + uppercasedType + "()").build() + ); + } + typeMustGenerateSerializer.put(specialNativeType, false); + + typeTypes.put("-" + specialNativeType, + ClassName.get("it.cavallium.data.generator.nativedata", "Nullable" + specialNativeType) + ); + typeTypes.put("§" + specialNativeType, ArrayTypeName.of(typeTypes.get(specialNativeType))); + if (nextVersion.isPresent()) { + nextVersionTypeTypes.put("-" + specialNativeType, + ClassName.get("it.cavallium.data.generator.nativedata", "Nullable" + specialNativeType) + ); + nextVersionTypeTypes.put("§" + specialNativeType, ArrayTypeName.of(typeTypes.get(specialNativeType))); + } + typeOptionalSerializers.put("-" + specialNativeType, + ClassName.get("it.cavallium.data.generator.nativedata", + "Nullable" + specialNativeType + "Serializer" + ) + ); + typeOptionalSerializers.put("§" + specialNativeType, + ClassName.get("it.cavallium.data.generator.nativedata", + "Array" + specialNativeType + "Serializer" + ) + ); + typeSerializeStatement.put("-" + specialNativeType, + new SerializeCodeBlockGenerator(CodeBlock + .builder() + .add("$T.Nullable" + specialNativeType + "SerializerInstance.serialize(dataOutput, ", + ClassName.get(joinPackage(versionPackage, ""), "Version") + ) + .build(), CodeBlock.builder().add(")").build()) + ); + typeSerializeStatement.put("§" + specialNativeType, + new SerializeCodeBlockGenerator(CodeBlock + .builder() + .add("$T.Array" + specialNativeType + "SerializerInstance.serialize(dataOutput, ", + ClassName.get(joinPackage(versionPackage, ""), "Version") + ) + .build(), CodeBlock.builder().add(")").build()) + ); + typeDeserializeStatement.put("-" + specialNativeType, + CodeBlock + .builder() + .add("$T.Nullable" + specialNativeType + "SerializerInstance.deserialize(dataInput)", + ClassName.get(joinPackage(versionPackage, ""), "Version") + ) + .build() + ); + typeDeserializeStatement.put("§" + specialNativeType, + CodeBlock + .builder() + .add("$T.Array" + specialNativeType + "SerializerInstance.deserialize(dataInput)", + ClassName.get(joinPackage(versionPackage, ""), "Version") + ) + .build() + ); + typeMustGenerateSerializer.put("-" + specialNativeType, false); + typeMustGenerateSerializer.put("§" + specialNativeType, false); + + } + + // Setup only the basic types upgraders variables + for (String s : versionConfiguration.classes.keySet()) { + if (nextVersion.isPresent()) { + typeOptionalUpgraders.put(s, ClassName.get(joinPackage(versionPackage, "upgraders"), s + "Upgrader")); + } + } + + // Generate the basic and super types + Stream + .concat(versionConfiguration.classes.keySet().stream(), versionConfiguration.superTypes.keySet().stream()) + .forEach((type) -> { + typeOptionalSerializers.put(type, + ClassName.get(joinPackage(versionPackage, "serializers"), type + "Serializer") + ); + typeSerializeStatement.put(type, + new SerializeCodeBlockGenerator(CodeBlock + .builder() + .add("$T." + type + "SerializerInstance.serialize(dataOutput, ", versionClassType) + .build(), CodeBlock.builder().add(")").build()) + ); + typeDeserializeStatement.put(type, + CodeBlock + .builder() + .add("$T." + type + "SerializerInstance.deserialize(dataInput)", versionClassType) + .build() + ); + typeMustGenerateSerializer.put(type, true); + typeTypes.put(type, ClassName.get(joinPackage(versionPackage, "data"), type)); + if (nextVersion.isPresent()) { + nextVersionTypeTypes.put(type, ClassName.get(joinPackage(nextVersionPackage.get(), "data"), type)); + } + + NeededTypes neededTypes = registerNeededTypes(versionConfiguration, + type, + nextVersion, + nextVersionPackage, + versionClassType, + versionPackage, + typeOptionalSerializers, + typeSerializeStatement, + typeDeserializeStatement, + typeMustGenerateSerializer, + typeTypes, + nextVersionTypeTypes, + () -> ClassName.get(joinPackage(versionPackage, "data"), type), + () -> ClassName.get(joinPackage(nextVersionPackage.orElseThrow(), "data"), type) + ); + }); + + // Generate the special types + for (Entry entry : versionConfiguration.customTypes.entrySet()) { + String key = entry.getKey(); + CustomTypesConfiguration customTypeConfiguration = entry.getValue(); + typeOptionalSerializers.put(key, ClassName.bestGuess(customTypeConfiguration.serializer)); + typeSerializeStatement.put(key, + new SerializeCodeBlockGenerator(CodeBlock + .builder() + .add("$T." + key + "SerializerInstance.serialize(dataOutput, ", versionClassType) + .build(), CodeBlock.builder().add(")").build()) + ); + typeDeserializeStatement.put(key, + CodeBlock + .builder() + .add("$T." + key + "SerializerInstance.deserialize(dataInput)", versionClassType) + .build() + ); + typeMustGenerateSerializer.put(key, false); + typeTypes.put(key, ClassName.bestGuess(customTypeConfiguration.javaClass)); + if (nextVersion.isPresent()) { + nextVersionTypeTypes.put(key, ClassName.bestGuess(customTypeConfiguration.javaClass)); + } + + var arrayClassName = ClassName.bestGuess(customTypeConfiguration.javaClass); + var neededTypes = registerNeededTypes(versionConfiguration, + key, + nextVersion, + nextVersionPackage, + versionClassType, + versionPackage, + typeOptionalSerializers, + typeSerializeStatement, + typeDeserializeStatement, + typeMustGenerateSerializer, + typeTypes, + nextVersionTypeTypes, + () -> arrayClassName, + () -> arrayClassName + ); + } + + for (String type : typeTypes.keySet()) { + var a1 = typeSerializeStatement.get(type).toString().replace("\n", ""); + var a2 = typeDeserializeStatement.get(type).toString().replace("\n", ""); + var a3 = typeTypes.get(type).toString().replace("\n", ""); + if (logger.isDebugEnabled()) { + logger.debug("Found type: {\n\ttype: \"" + type + "\",\n\tclass: \"" + a3 + "\",\n\tserialize: \"" + a1 + + "\",\n\tdeserialize: \"" + a2 + "\"\n}"); + } else { + switch (type.charAt(0)) { + case '$': + logger.debug("Found array: " + type.substring(1)); + break; + case '-': + logger.debug("Found nullable type: " + type.substring(1)); + break; + default: + logger.debug("Found type: " + type); + break; + } + } + } + } + + // Check if all types exist + { + for (Entry e : versionConfiguration.classes.entrySet()) { + String type = e.getKey(); + ClassConfiguration 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 + "'"); + } + } + } + } + + // Generate the nullable types and serializers + { + for (String type : typeTypes.keySet()) { + if (type.startsWith("-")) { + if (typeMustGenerateSerializer.get(type)) { + String substring = type.substring(1); + var nullableClassType = ClassName.get(joinPackage(versionPackage, "data.nullables"), + "Nullable" + substring + ); + + // Create the nullable X serializer class + { + var nullableSerializerClass = TypeSpec.classBuilder("Nullable" + substring + "Serializer"); + nullableSerializerClass.addModifiers(Modifier.PUBLIC); + nullableSerializerClass.addModifiers(Modifier.FINAL); + nullableSerializerClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( + "it.cavallium.data.generator", + "DataSerializer" + ), nullableClassType)); + // Create the INSTANCE field + { + var thisSerializerClassType = ClassName.get(joinPackage(versionPackage, "serializers"), + "Nullable" + substring + "Serializer" + ); + var instanceField = FieldSpec + .builder(thisSerializerClassType, "INSTANCE") + .initializer("new $T()", thisSerializerClassType); + instanceField.addModifiers(Modifier.PUBLIC); + instanceField.addModifiers(Modifier.STATIC); + instanceField.addModifiers(Modifier.FINAL); + nullableSerializerClass.addField(instanceField.build()); + } + // Create the serialize method + { + var serializeMethod = createEmptySerializeMethod(nullableClassType); + serializeMethod.beginControlFlow("if (data.isEmpty())"); + serializeMethod.addStatement("dataOutput.writeBoolean(false)"); + serializeMethod.nextControlFlow("else"); + serializeMethod.addStatement("dataOutput.writeBoolean(true)"); + serializeMethod.addStatement("var dataContent = data.get()"); + serializeMethod.addStatement(typeSerializeStatement.get(substring).generate("dataContent")); + serializeMethod.endControlFlow(); + nullableSerializerClass.addMethod(serializeMethod.build()); + } + // Create the deserialize method + { + var deserializeMethod = createEmptyDeserializeMethod(nullableClassType); + deserializeMethod.addStatement("var isPresent = dataInput.readBoolean()"); + deserializeMethod.beginControlFlow("if (!isPresent)"); + deserializeMethod.addStatement("return $T.empty()", nullableClassType); + deserializeMethod.nextControlFlow("else"); + deserializeMethod.addCode("$[return $T.of(", nullableClassType); + deserializeMethod.addCode(typeDeserializeStatement.get(substring)); + deserializeMethod.addCode(");\n$]"); + deserializeMethod.endControlFlow(); + nullableSerializerClass.addMethod(deserializeMethod.build()); + } + // Save the resulting class in the main package + try { + writeClass(outPath, joinPackage(versionPackage, "serializers"), nullableSerializerClass); + } catch (IOException e) { + throw new IOError(e); + } + } + + // Create the nullable X types classes + { + var typeType = typeTypes.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); + nullableTypeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data.nullables"), + "INullableBasicType" + )); + nullableTypeClass.addSuperinterface(IGenericNullable.class); + var constructor = MethodSpec.constructorBuilder(); + constructor.addModifiers(Modifier.PUBLIC); + constructor.addParameter(ParameterSpec.builder(typeType, "value").build()); + constructor.addStatement("this.value = value"); + nullableTypeClass.addMethod(constructor.build()); + var valueField = FieldSpec.builder(typeType, "value"); + nullableTypeClass.addField(valueField.build()); + var ofMethod = MethodSpec.methodBuilder("of"); + ofMethod.addModifiers(Modifier.PUBLIC); + ofMethod.addModifiers(Modifier.STATIC); + ofMethod.addModifiers(Modifier.FINAL); + ofMethod.addException(NullPointerException.class); + ofMethod.returns(nullableClassType); + ofMethod.addParameter(ParameterSpec.builder(typeType, "value").build()); + ofMethod.beginControlFlow("if (value == null)"); + ofMethod.addStatement("throw new $T()", NullPointerException.class); + ofMethod.nextControlFlow("else"); + ofMethod.addStatement("return new $T(value)", nullableTypeType); + ofMethod.endControlFlow(); + nullableTypeClass.addMethod(ofMethod.build()); + var ofNullableMethod = MethodSpec.methodBuilder("ofNullable"); + ofNullableMethod.addModifiers(Modifier.PUBLIC); + ofNullableMethod.addModifiers(Modifier.STATIC); + ofNullableMethod.addModifiers(Modifier.FINAL); + ofNullableMethod.returns(nullableClassType); + ofNullableMethod.addParameter(ParameterSpec.builder(typeType, "value").build()); + ofNullableMethod.addStatement("return new $T(value)", nullableTypeType); + nullableTypeClass.addMethod(ofNullableMethod.build()); + var emptyMethod = MethodSpec.methodBuilder("empty"); + emptyMethod.addModifiers(Modifier.PUBLIC); + emptyMethod.addModifiers(Modifier.STATIC); + emptyMethod.addModifiers(Modifier.FINAL); + emptyMethod.returns(nullableClassType); + emptyMethod.addStatement("return new $T(null)", nullableTypeType); + nullableTypeClass.addMethod(emptyMethod.build()); + var isEmptyMethod = MethodSpec.methodBuilder("isEmpty"); + isEmptyMethod.addModifiers(Modifier.PUBLIC); + isEmptyMethod.addModifiers(Modifier.FINAL); + isEmptyMethod.returns(TypeName.BOOLEAN); + isEmptyMethod.addStatement("return value == null"); + nullableTypeClass.addMethod(isEmptyMethod.build()); + var isPresentMethod = MethodSpec.methodBuilder("isPresent"); + isPresentMethod.addModifiers(Modifier.PUBLIC); + isPresentMethod.addModifiers(Modifier.FINAL); + isPresentMethod.returns(TypeName.BOOLEAN); + isPresentMethod.addStatement("return value != null"); + nullableTypeClass.addMethod(isPresentMethod.build()); + var getMethod = MethodSpec.methodBuilder("get"); + getMethod.addModifiers(Modifier.PUBLIC); + getMethod.addModifiers(Modifier.FINAL); + getMethod.addException(NullPointerException.class); + getMethod.addAnnotation(NotNull.class); + getMethod.addAnnotation(NonNull.class); + getMethod.returns(typeType); + getMethod.beginControlFlow("if (value == null)"); + getMethod.addStatement("throw new $T()", NullPointerException.class); + getMethod.nextControlFlow("else"); + getMethod.addStatement("return value"); + getMethod.endControlFlow(); + nullableTypeClass.addMethod(getMethod.build()); + var orElseMethod = MethodSpec.methodBuilder("orElse"); + orElseMethod.addParameter(ParameterSpec + .builder(typeType, "defaultValue") + .addAnnotation(NotNull.class) + .addAnnotation(NonNull.class) + .build()); + orElseMethod.addModifiers(Modifier.PUBLIC); + orElseMethod.addModifiers(Modifier.FINAL); + orElseMethod.addException(NullPointerException.class); + orElseMethod.addAnnotation(NotNull.class); + orElseMethod.addAnnotation(NonNull.class); + orElseMethod.returns(typeType); + orElseMethod.beginControlFlow("if (value == null)"); + orElseMethod.addStatement("return defaultValue"); + orElseMethod.nextControlFlow("else"); + orElseMethod.addStatement("return value"); + orElseMethod.endControlFlow(); + nullableTypeClass.addMethod(orElseMethod.build()); + var getNullableMethod = MethodSpec.methodBuilder("getNullable"); + getNullableMethod.addModifiers(Modifier.PUBLIC); + getNullableMethod.addModifiers(Modifier.FINAL); + getNullableMethod.addAnnotation(Nullable.class); + getNullableMethod.returns(typeType); + getNullableMethod.addStatement("return value"); + nullableTypeClass.addMethod(getNullableMethod.build()); + var getDollarNullableMethod = MethodSpec.methodBuilder("$getNullable"); + getDollarNullableMethod.addModifiers(Modifier.PUBLIC); + getDollarNullableMethod.addModifiers(Modifier.FINAL); + getDollarNullableMethod.addAnnotation(Nullable.class); + getDollarNullableMethod.addAnnotation(Override.class); + getDollarNullableMethod.returns(typeType); + getDollarNullableMethod.addStatement("return this.getNullable()"); + nullableTypeClass.addMethod(getDollarNullableMethod.build()); + + try { + writeClass(outPath, joinPackage(versionPackage, "data.nullables"), nullableTypeClass); + } catch (IOException e) { + throw new IOError(e); + } + } + } + } + } + } + + // Generate the array types and serializers + { + for (String type : typeTypes.keySet()) { + if (type.startsWith("§")) { + if (typeMustGenerateSerializer.get(type)) { + String substring = type.substring(1); + var classType = ClassName.get(joinPackage(versionPackage, "data"), substring); + var arrayClassType = ArrayTypeName.of(classType); + + // Create the array X serializer class + { + var arraySerializerClass = TypeSpec.classBuilder("Array" + substring + "Serializer"); + arraySerializerClass.addModifiers(Modifier.PUBLIC); + arraySerializerClass.addModifiers(Modifier.FINAL); + arraySerializerClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( + "it.cavallium.data.generator", + "DataSerializer" + ), arrayClassType)); + // Create the INSTANCE field + { + var thisSerializerClassType = ClassName.get(joinPackage(versionPackage, "serializers"), + "Array" + substring + "Serializer" + ); + var instanceField = FieldSpec + .builder(thisSerializerClassType, "INSTANCE") + .initializer("new $T()", thisSerializerClassType); + instanceField.addModifiers(Modifier.PUBLIC); + instanceField.addModifiers(Modifier.STATIC); + instanceField.addModifiers(Modifier.FINAL); + arraySerializerClass.addField(instanceField.build()); + } + // Create the serialize method + { + var serializeMethod = createEmptySerializeMethod(arrayClassType); + serializeMethod.addStatement("dataOutput.writeInt(data.length)"); + serializeMethod.beginControlFlow("for (int i = 0; i < data.length; i++)"); + serializeMethod.addStatement(typeSerializeStatement.get(substring).generate("data[i]")); + serializeMethod.endControlFlow(); + arraySerializerClass.addMethod(serializeMethod.build()); + } + // Create the deserialize method + { + var deserializeMethod = createEmptyDeserializeMethod(arrayClassType); + deserializeMethod.addStatement("var data = new $T[dataInput.readInt()]", classType); + deserializeMethod.beginControlFlow("for (int i = 0; i < data.length; i++)"); + deserializeMethod.addStatement(CodeBlock.join(List.of(CodeBlock.of("data[i] ="), + typeDeserializeStatement.get(substring) + ), " ")); + deserializeMethod.endControlFlow(); + deserializeMethod.addStatement("return data"); + arraySerializerClass.addMethod(deserializeMethod.build()); + } + // Save the resulting class in the main package + try { + writeClass(outPath, joinPackage(versionPackage, "serializers"), arraySerializerClass); + } catch (IOException e) { + throw new IOError(e); + } + } + } + } + } + } + + // Generate the basic types serializers + { + for (Entry classConfigurationEntry : versionConfiguration.classes.entrySet()) { + String type = classConfigurationEntry.getKey(); + ClassConfiguration basicTypeConfiguration = classConfigurationEntry.getValue(); + var classType = ClassName.get(joinPackage(versionPackage, "data"), type); + + // Create the basic X serializer class + { + var serializerClass = TypeSpec.classBuilder(type + "Serializer"); + serializerClass.addModifiers(Modifier.PUBLIC); + serializerClass.addModifiers(Modifier.FINAL); + serializerClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( + "it.cavallium.data.generator", + "DataSerializer" + ), classType)); + // Create the INSTANCE field + { + var thisSerializerClassType = ClassName.get(joinPackage(versionPackage, "serializers"), + type + "Serializer" + ); + var instanceField = FieldSpec + .builder(thisSerializerClassType, "INSTANCE") + .initializer("new $T()", thisSerializerClassType); + instanceField.addModifiers(Modifier.PUBLIC); + instanceField.addModifiers(Modifier.STATIC); + instanceField.addModifiers(Modifier.FINAL); + serializerClass.addField(instanceField.build()); + } + // Create the serialize method + { + var serializeMethod = createEmptySerializeMethod(classType); + if (basicTypeConfiguration.data != null) { + for (Entry entry : basicTypeConfiguration.data.entrySet()) { + String field = entry.getKey(); + String fieldType = entry.getValue(); + serializeMethod.addStatement(typeSerializeStatement + .get(fieldType) + .generate("data.get" + capitalize(field) + "()")); + } + } + serializerClass.addMethod(serializeMethod.build()); + } + // Create the deserialize method + { + var deserializeMethod = createEmptyDeserializeMethod(classType); + deserializeMethod.addCode("$[return $T.of(\n$]", classType); + deserializeMethod.addCode("$>"); + AtomicInteger i = new AtomicInteger(basicTypeConfiguration.data.size()); + for (Entry entry : basicTypeConfiguration.data.entrySet()) { + String field = entry.getKey(); + String fieldType = entry.getValue(); + boolean isLast = i.decrementAndGet() == 0; + deserializeMethod.addCode(typeDeserializeStatement.get(fieldType)).addCode((isLast ? "" : ",") + "\n"); + } + deserializeMethod.addCode("$<"); + deserializeMethod.addStatement(")"); + serializerClass.addMethod(deserializeMethod.build()); + } + // Save the resulting class in the main package + try { + writeClass(outPath, joinPackage(versionPackage, "serializers"), serializerClass); + } catch (IOException e) { + throw new IOError(e); + } + } + + // Create the basic X upgrader class + { + if (nextVersion.isPresent()) { + var nextVersionClassType = ClassName.get(joinPackage(nextVersionPackage.get(), "data"), type); + + var upgraderClass = TypeSpec.classBuilder(type + "Upgrader"); + upgraderClass.addModifiers(Modifier.PUBLIC); + upgraderClass.addModifiers(Modifier.FINAL); + upgraderClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( + "it.cavallium.data.generator", + "DataUpgrader" + ), classType, nextVersionClassType)); + // Create the INSTANCE field + { + var thisUpgraderClassType = ClassName.get(joinPackage(versionPackage, "upgraders"), type + "Upgrader"); + var instanceField = FieldSpec + .builder(thisUpgraderClassType, "INSTANCE") + .initializer("new $T()", thisUpgraderClassType); + instanceField.addModifiers(Modifier.PUBLIC); + instanceField.addModifiers(Modifier.STATIC); + instanceField.addModifiers(Modifier.FINAL); + upgraderClass.addField(instanceField.build()); + } + // Create the upgrade method + { + var deserializeMethod = MethodSpec.methodBuilder("upgrade"); + deserializeMethod.addAnnotation(NotNull.class); + deserializeMethod.addAnnotation(NonNull.class); + deserializeMethod.addAnnotation(Override.class); + deserializeMethod.addModifiers(Modifier.PUBLIC); + deserializeMethod.addModifiers(Modifier.FINAL); + deserializeMethod.returns(nextVersionClassType); + deserializeMethod.addParameter(ParameterSpec + .builder(classType, "data") + .addAnnotation(NotNull.class) + .addAnnotation(NonNull.class) + .build()); + deserializeMethod.addException(IOException.class); + Object2IntLinkedOpenHashMap currentVarNumber = new Object2IntLinkedOpenHashMap<>( + basicTypeConfiguration.getData().size()); + ObjectOpenHashSet currentVarDeleted = new ObjectOpenHashSet<>(); + currentVarNumber.defaultReturnValue(-1); + deserializeMethod.addStatement("$T.requireNonNull(data)", Objects.class); + for (Entry stringStringEntry : basicTypeConfiguration.getData().entrySet()) { + String k = stringStringEntry.getKey(); + String value = stringStringEntry.getValue(); + currentVarNumber.addTo(k, 1); + deserializeMethod.addStatement("var $$field$$" + 0 + "$$" + k + " = data.get" + capitalize(k) + "()"); + } + + List list = new ArrayList<>(); + for (VersionTransformation versionTransformation : configuration.versions.get(nextVersion.get()).transformations) { + if (versionTransformation.isForClass(type)) { + list.add(versionTransformation); + } + } + var transformations = Collections.unmodifiableList(list); + AtomicInteger transformationNumber = new AtomicInteger(0); + HashMap currentTransformedFieldTypes = new HashMap<>(); + for (Entry stringClassConfigurationEntry : versionConfiguration.classes.entrySet()) { + String className = stringClassConfigurationEntry.getKey(); + ClassConfiguration classConfiguration = stringClassConfigurationEntry.getValue(); + for (Entry entry : classConfiguration.getData().entrySet()) { + String fieldName = entry.getKey(); + String fieldType = entry.getValue(); + currentTransformedFieldTypes.put(className + "." + fieldName, typeTypes.get(fieldType)); + } + } + for (VersionTransformation transformationConfig : transformations) { + var transformation = transformationConfig.getTransformation(); + deserializeMethod.addCode("\n"); + deserializeMethod.addComment("TRANSFORMATION #" + transformationNumber.incrementAndGet() + ": " + + transformation.getTransformName()); + switch (transformation.getTransformName()) { + case "remove-data": + var removeDataTransformation = (RemoveDataConfiguration) transformation; + { + deserializeMethod.addComment( + "Deleted $$field$$" + currentVarNumber.getInt(removeDataTransformation.from) + "$$" + + removeDataTransformation.from); + currentVarNumber.addTo(removeDataTransformation.from, 1); + currentVarDeleted.add(removeDataTransformation.from); + } + + Objects.requireNonNull(currentTransformedFieldTypes.remove( + removeDataTransformation.transformClass + "." + removeDataTransformation.from)); + break; + case "move-data": + var moveDataTransformation = (MoveDataConfiguration) transformation; + + { + currentVarNumber.addTo(moveDataTransformation.to, 1); + currentVarDeleted.remove(moveDataTransformation.to); + deserializeMethod.addStatement( + "var $$field$$" + currentVarNumber.getInt(moveDataTransformation.to) + "$$" + + moveDataTransformation.to + " = $$field$$" + currentVarNumber.getInt( + moveDataTransformation.from) + "$$" + moveDataTransformation.from); + } + { + deserializeMethod.addComment( + "Deleted $$field$$" + currentVarNumber.getInt(moveDataTransformation.from) + "$$" + + moveDataTransformation.from); + currentVarNumber.addTo(moveDataTransformation.from, 1); + currentVarDeleted.add(moveDataTransformation.from); + } + + currentTransformedFieldTypes.put( + moveDataTransformation.transformClass + "." + moveDataTransformation.to, + Objects.requireNonNull(currentTransformedFieldTypes.remove( + moveDataTransformation.transformClass + "." + moveDataTransformation.from)) + ); + break; + case "upgrade-data": + var upgradeDataTransformation = (UpgradeDataConfiguration) transformation; + TypeName fromType = currentTransformedFieldTypes.get( + upgradeDataTransformation.transformClass + "." + upgradeDataTransformation.from); + TypeName fromTypeBoxed = fromType.isPrimitive() ? fromType.box() : fromType; + String toTypeName = configuration.versions.get(nextVersion.get()).classes + .get(upgradeDataTransformation.transformClass) + .getData() + .get(upgradeDataTransformation.from); + TypeName toType = nextVersionTypeTypes.get(toTypeName); + TypeName toTypeBoxed = toType.isPrimitive() ? toType.box() : toType; + deserializeMethod.addStatement( + "$T $$field$$" + (currentVarNumber.getInt(upgradeDataTransformation.from) + 1) + "$$" + + upgradeDataTransformation.from, toType); + deserializeMethod.beginControlFlow("try"); + deserializeMethod.addStatement("var upgrader = (($T) new $T())", + ParameterizedTypeName.get(ClassName.get(DataUpgrader.class), fromTypeBoxed, toTypeBoxed), + ClassName.bestGuess(upgradeDataTransformation.upgrader) + ); + deserializeMethod.addStatement( + "$T upgraded = ($T) upgrader.upgrade(($T) $$field$$" + currentVarNumber.getInt( + upgradeDataTransformation.from) + "$$" + upgradeDataTransformation.from + ")", + toType, + toTypeBoxed, + fromTypeBoxed + ); + + deserializeMethod.addStatement( + "$$field$$" + (currentVarNumber.getInt(upgradeDataTransformation.from) + 1) + "$$" + + upgradeDataTransformation.from + " = upgraded"); + deserializeMethod.nextControlFlow(" catch ($T e)", ClassCastException.class); + deserializeMethod.addStatement("throw new $T(e)", IllegalArgumentException.class); + deserializeMethod.endControlFlow(); + Objects.requireNonNull(currentTransformedFieldTypes.remove( + upgradeDataTransformation.transformClass + "." + upgradeDataTransformation.from)); + currentTransformedFieldTypes.put( + upgradeDataTransformation.transformClass + "." + upgradeDataTransformation.from, toType); + + currentVarNumber.addTo(upgradeDataTransformation.from, 1); + currentVarDeleted.remove(upgradeDataTransformation.from); + break; + case "new-data": + var newDataTransformation = (NewDataConfiguration) transformation; + String newTypeName = configuration.versions.get(nextVersion.get()).classes + .get(newDataTransformation.transformClass) + .getData() + .get(newDataTransformation.to); + TypeName newType = nextVersionTypeTypes.get(newTypeName); + TypeName newTypeBoxed = newType.isPrimitive() ? newType.box() : newType; + { + currentVarNumber.addTo(newDataTransformation.to, 1); + currentVarDeleted.remove(newDataTransformation.to); + deserializeMethod.addStatement( + "var $$field$$" + currentVarNumber.getInt(newDataTransformation.to) + "$$" + + newDataTransformation.to + " = (($T) new $T()).initialize()", + ParameterizedTypeName.get(ClassName.get(DataInitializer.class), newTypeBoxed), + ClassName.bestGuess(newDataTransformation.initializer) + ); + } + if (currentTransformedFieldTypes.put( + newDataTransformation.transformClass + "." + newDataTransformation.to, newType) != null) { + throw new IllegalStateException(); + } + break; + default: + throw new UnsupportedOperationException( + "Unknown transform type: " + transformation.getTransformName()); + } + } + deserializeMethod.addCode("\n"); + deserializeMethod.addComment( + "Upgrade the remaining untouched values to the new version before returning"); + + var nextVersionFieldTypes = configuration.versions.get(nextVersion.get()).classes.get(type).getData(); + for (var e : currentVarNumber.object2IntEntrySet()) { + String key = e.getKey(); + int number = e.getIntValue(); + if (!currentVarDeleted.contains(key)) { + String toTypeName = nextVersionFieldTypes.get(key); + TypeName toType = nextVersionTypeTypes.get(toTypeName); + TypeName toTypeBoxed = toType.isPrimitive() ? toType.box() : toType; + { + currentVarNumber.addTo(key, 1); + currentVarDeleted.remove(key); + deserializeMethod.addStatement( + "$T $$field$$" + (number + 1) + "$$" + key + " = ($T) " + "upgradeUnknownField($$field$$" + + number + "$$" + key + ")", toType, toTypeBoxed); + } + } + } + + deserializeMethod.addCode("return $T.of(", nextVersionClassType); + AtomicBoolean isFirst = new AtomicBoolean(true); + for (Entry entry : nextVersionFieldTypes.entrySet()) { + String field = entry.getKey(); + String fieldType = entry.getValue(); + if (!isFirst.getAndSet(false)) { + deserializeMethod.addCode(", "); + } + deserializeMethod.addCode("$$field$$" + currentVarNumber.getInt(field) + "$$" + field); + } + 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 (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 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 (value instanceof $T)", oldIBasicType); + upgradeUnknownField.addStatement("return ($T) $T.upgradeToNextVersion(($T) value)", + newIBasicType, + oldVersionType, + oldIBasicType + ); + 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); + } catch (IOException e) { + throw new IOError(e); + } + } + } + + } + } + + // Generate the super types serializers + { + for (Entry> entry : versionConfiguration.superTypes.entrySet()) { + String type = entry.getKey(); + Set superTypeConfiguration = entry.getValue(); + var classType = ClassName.get(joinPackage(versionPackage, "data"), type); + + // Create the super X serializer class + { + var serializerClass = TypeSpec.classBuilder(type + "Serializer"); + serializerClass.addModifiers(Modifier.PUBLIC); + serializerClass.addModifiers(Modifier.FINAL); + serializerClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get( + "it.cavallium.data.generator", + "DataSerializer" + ), classType)); + // Create the INSTANCE field + { + var thisSerializerClassType = ClassName.get(joinPackage(versionPackage, "serializers"), + type + "Serializer" + ); + var instanceField = FieldSpec + .builder(thisSerializerClassType, "INSTANCE") + .initializer("new $T()", thisSerializerClassType); + instanceField.addModifiers(Modifier.PUBLIC); + instanceField.addModifiers(Modifier.STATIC); + instanceField.addModifiers(Modifier.FINAL); + serializerClass.addField(instanceField.build()); + } + // Create the checkIdValidity method + { + var checkIdValidityMethod = MethodSpec.methodBuilder("checkIdValidity"); + checkIdValidityMethod.addModifiers(Modifier.PUBLIC); + checkIdValidityMethod.addModifiers(Modifier.STATIC); + checkIdValidityMethod.addModifiers(Modifier.FINAL); + checkIdValidityMethod.returns(TypeName.VOID); + checkIdValidityMethod.addParameter(ParameterSpec.builder(TypeName.INT, "id").build()); + checkIdValidityMethod.addException(IOException.class); + checkIdValidityMethod.addStatement("if (id < 0) throw new $T(new $T(id))", + IOException.class, + IndexOutOfBoundsException.class + ); + checkIdValidityMethod.addStatement( + "if (id >= " + superTypeConfiguration.size() + ") throw new $T(new $T(id))", + IOException.class, + IndexOutOfBoundsException.class + ); + serializerClass.addMethod(checkIdValidityMethod.build()); + } + // Create the serialize method + { + var serializeMethod = createEmptySerializeMethod(classType); + serializeMethod.addStatement("int id = data.getMetaId$$" + type + "()"); + serializeMethod.addCode("\n"); + serializeMethod.addStatement("checkIdValidity(id)"); + serializeMethod.addCode("\n"); + serializeMethod.addStatement("dataOutput.writeByte(id)"); + serializeMethod.addCode("\n"); + serializeMethod.beginControlFlow("switch (id)"); + int i = 0; + for (String subType : superTypeConfiguration) { + serializeMethod.beginControlFlow("case " + i + ":"); + serializeMethod.addStatement("$T." + subType + "SerializerInstance.serialize(dataOutput, ($T) data)", + versionClassType, + ClassName.get(joinPackage(versionPackage, "data"), subType) + ); + serializeMethod.addStatement("break"); + serializeMethod.endControlFlow(); + i++; + } + serializeMethod.addStatement("default: throw new $T(new $T())", + IOException.class, + IndexOutOfBoundsException.class + ); + serializeMethod.endControlFlow(); + serializerClass.addMethod(serializeMethod.build()); + } + // Create the deserialize method + { + var deserializeMethod = createEmptyDeserializeMethod(classType); + deserializeMethod.addStatement("int id = dataInput.readUnsignedByte()"); + deserializeMethod.addCode("\n"); + deserializeMethod.addStatement("checkIdValidity(id)"); + deserializeMethod.addCode("\n"); + deserializeMethod.beginControlFlow("switch (id)"); + int i = 0; + for (String subType : superTypeConfiguration) { + deserializeMethod.addStatement("case " + i + ": return " + typeDeserializeStatement.get(subType)); + i++; + } + deserializeMethod.addStatement("default: throw new $T(new $T())", + IOException.class, + IndexOutOfBoundsException.class + ); + deserializeMethod.endControlFlow(); + serializerClass.addMethod(deserializeMethod.build()); + } + // Save the resulting class in the main package + try { + writeClass(outPath, joinPackage(versionPackage, "serializers"), serializerClass); + } catch (IOException e) { + throw new IOError(e); + } + } + } + } + + // Create the Version class + { + var versionClass = TypeSpec.classBuilder("Version"); + versionClass.addSuperinterface(ParameterizedTypeName.get(ClassName.get(joinPackage(basePackageName, ""), "IVersion"), + ClassName.get(joinPackage(versionPackage, "data"), "IBasicType") + )); + versionClass.addModifiers(Modifier.PUBLIC); + versionClass.addModifiers(Modifier.FINAL); + // Add a static variable for the current version + { + var versionNumberXField = FieldSpec + .builder(TypeName.INT, "VERSION") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.STATIC) + .addModifiers(Modifier.FINAL) + .initializer("$T." + getVersionVarName(version), + ClassName.get(joinPackage(basePackageName, ""), "Versions") + ) + .build(); + versionClass.addField(versionNumberXField); + } + // Add a static instance for the current version + { + var versionInstanceField = FieldSpec + .builder(versionClassType, "INSTANCE") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.STATIC) + .addModifiers(Modifier.FINAL) + .initializer("new $T()", versionClassType) + .build(); + versionClass.addField(versionInstanceField); + } + // Add upgrader instances static fields + { + for (Entry entry : typeOptionalUpgraders.entrySet()) { + String type = entry.getKey(); + TypeName upgraderType = entry.getValue(); + var typeName = type; + if (type.startsWith("§")) { + typeName = "Array" + type.substring(1); + } else if (type.startsWith("-")) { + typeName = "Nullable" + type.substring(1); + } else { + typeName = type; + } + var upgraderStaticField = FieldSpec.builder(upgraderType, typeName + "UpgraderInstance"); + upgraderStaticField.addModifiers(Modifier.PUBLIC); + upgraderStaticField.addModifiers(Modifier.STATIC); + upgraderStaticField.addModifiers(Modifier.FINAL); + upgraderStaticField.initializer("new $T()", upgraderType); + versionClass.addField(upgraderStaticField.build()); + } + } + // Add serializer instances static fields + { + for (Entry entry : typeOptionalSerializers.entrySet()) { + String type = entry.getKey(); + TypeName serializerType = entry.getValue(); + var typeName = type; + if (type.startsWith("§")) { + typeName = "Array" + type.substring(1); + } else if (type.startsWith("-")) { + typeName = "Nullable" + type.substring(1); + } else { + typeName = type; + } + var serializerStaticField = FieldSpec.builder(serializerType, typeName + "SerializerInstance"); + serializerStaticField.addModifiers(Modifier.PUBLIC); + serializerStaticField.addModifiers(Modifier.STATIC); + serializerStaticField.addModifiers(Modifier.FINAL); + serializerStaticField.initializer("new $T()", serializerType); + versionClass.addField(serializerStaticField.build()); + } + } + // Add upgradeToNextVersion method + { + if (nextVersion.isPresent()) { + var upgradeToNextVersionMethodBuilder = MethodSpec + .methodBuilder("upgradeToNextVersion") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.STATIC) + .addModifiers(Modifier.FINAL) + .returns(ClassName.get(joinPackage(nextVersionPackage.get(), "data"), "IBasicType")) + .addException(IOException.class) + .addParameter(ParameterSpec + .builder(ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"), "oldData") + .build()) + .beginControlFlow("switch (oldData.getBasicType$$()) "); + for (Entry entry : versionConfiguration.classes.entrySet()) { + String type = entry.getKey(); + ClassConfiguration typeConfiguration = entry.getValue(); + var data = typeConfiguration.data; + upgradeToNextVersionMethodBuilder.addStatement( + "case " + type + ": return $T." + type + "UpgraderInstance.upgrade(($T) oldData)", + versionClassType, + ClassName.get(joinPackage(versionPackage, "data"), type) + ); + } + var upgradeToNextVersionMethod = upgradeToNextVersionMethodBuilder + .beginControlFlow("default: ") + .addStatement("throw new $T(\"Unknown type: \" + oldData.getBasicType$$())", IOException.class) + .endControlFlow() + .endControlFlow() + .build(); + versionClass.addMethod(upgradeToNextVersionMethod); + } + } + // Add getClass method + { + var getClassMethodBuilder = MethodSpec + .methodBuilder("getClass") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(ParameterizedTypeName.get(ClassName.get(Class.class), + WildcardTypeName.subtypeOf(ClassName.get(joinPackage(versionPackage, "data"), "IBasicType")) + )) + .addParameter(ParameterSpec + .builder(ClassName.get(joinPackage(basePackageName, ""), "BasicType"), "type") + .build()) + .beginControlFlow("switch (type)"); + for (Entry entry : versionConfiguration.classes.entrySet()) { + String type = entry.getKey(); + ClassConfiguration typeConfiguration = entry.getValue(); + var data = typeConfiguration.data; + + getClassMethodBuilder.addStatement("case " + type + ": return $T.class", + ClassName.get(joinPackage(versionPackage, "data"), type) + ); + } + getClassMethodBuilder + .beginControlFlow("default: ") + .addStatement("throw new $T(\"Unknown type: \" + type)", IllegalArgumentException.class) + .endControlFlow() + .endControlFlow(); + versionClass.addMethod(getClassMethodBuilder.build()); + } + // Add getSerializer method + { + var getSerializerMethodBuilder = MethodSpec + .methodBuilder("getSerializer") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addTypeVariable(TypeVariableName.get("T", + ClassName.get(joinPackage(versionPackage, "data"), "IBasicType") + )) + .returns(ParameterizedTypeName.get(ClassName.get(DataSerializer.class), TypeVariableName.get("T"))) + .addException(IOException.class) + .addParameter(ParameterSpec + .builder(ClassName.get(joinPackage(basePackageName, ""), "BasicType"), "type") + .build()) + .beginControlFlow("switch (type)"); + for (Entry entry : versionConfiguration.classes.entrySet()) { + String type = entry.getKey(); + ClassConfiguration typeConfiguration = entry.getValue(); + var data = typeConfiguration.data; + getSerializerMethodBuilder.addStatement("case " + type + ": return ($T) $T." + type + "SerializerInstance", + ParameterizedTypeName.get(ClassName.get(DataSerializer.class), TypeVariableName.get("T")), + versionClassType + ); + } + getSerializerMethodBuilder + .beginControlFlow("default: ") + .addStatement("throw new $T(\"Unknown type: \" + type)", IOException.class) + .endControlFlow() + .endControlFlow(); + versionClass.addMethod(getSerializerMethodBuilder.build()); + } + // Add createNullableOf method + { + var createNullableOfMethod = MethodSpec + .methodBuilder("createNullableOf") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL) + .returns(ClassName.get(joinPackage(versionPackage, "data.nullables"), "INullableBasicType")) + .addException(IOException.class) + .addParameter(ParameterSpec.builder(String.class, "type").build()) + .addParameter(ParameterSpec.builder(Object.class, "content").build()) + .beginControlFlow("switch (type)"); + for (String item : typeTypes.keySet()) { + if (item.startsWith("-")) { + String type = item.substring(1); + if (!specialNativeTypes.contains(type)) { + var nullableType = "Nullable" + type; + createNullableOfMethod.addStatement("case \"" + nullableType + "\": return $T.ofNullable(($T) content)", + typeTypes.get("-" + type), + typeTypes.get(type) + ); + } + } + } + createNullableOfMethod + .beginControlFlow("default: ") + .addStatement("throw new $T(\"Unknown nullable type: \" + type)", IOException.class) + .endControlFlow() + .endControlFlow(); + versionClass.addMethod(createNullableOfMethod.build()); + } + // Add createArrayOf method + { + var createArrayOfMethod = MethodSpec + .methodBuilder("createArrayOf") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL) + .returns(Object.class) + .addException(IOException.class) + .addParameter(ParameterSpec.builder(String.class, "type").build()) + .addParameter(ParameterSpec.builder(TypeName.INT, "length").build()) + .beginControlFlow("switch (type)"); + for (String item : typeTypes.keySet()) { + if (item.startsWith("§")) { + String type = item.substring(1); + if (!specialNativeTypes.contains(type)) { + createArrayOfMethod.addStatement("case \"" + type + "\": return new $T[length]", typeTypes.get(type)); + } + } + } + createArrayOfMethod + .beginControlFlow("default: ") + .addStatement("throw new $T(\"Unknown nullable type: \" + type)", IOException.class) + .endControlFlow() + .endControlFlow(); + versionClass.addMethod(createArrayOfMethod.build()); + } + // Add getVersion method + { + var getVersionMethod = MethodSpec + .methodBuilder("getVersion") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(TypeName.INT) + .addStatement("return VERSION") + .build(); + versionClass.addMethod(getVersionMethod); + } + // Save the resulting class in the main package + try { + writeClass(outPath, joinPackage(versionPackage, ""), versionClass); + } catch (IOException e) { + throw new IOError(e); + } + } + + // Create the interface IType + { + var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); + var iTypeInterface = TypeSpec.interfaceBuilder("IType"); + iTypeInterface.addModifiers(Modifier.PUBLIC); + iTypeInterface.addSuperinterface(ClassName.get(Serializable.class)); + try { + writeClass(outPath, joinPackage(versionPackage, "data"), iTypeInterface); + } catch (IOException e) { + throw new IOError(e); + } + } + + // Create the interface IBasicType + { + var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); + var ibasicTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IBasicType"); + var ibasicTypeInterface = TypeSpec.interfaceBuilder("IBasicType"); + ibasicTypeInterface.addModifiers(Modifier.PUBLIC); + ibasicTypeInterface.addSuperinterface(iTypeInterfaceType); + // Create getBasicType$type method + { + var getBasicTypeMethod = MethodSpec + .methodBuilder("getBasicType$") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .returns(ClassName.get(joinPackage(basePackageName, ""), "BasicType")); + ibasicTypeInterface.addMethod(getBasicTypeMethod.build()); + } + try { + writeClass(outPath, joinPackage(versionPackage, "data"), ibasicTypeInterface); + } catch (IOException e) { + throw new IOError(e); + } + } + + // Create the interface INullableBasicType + { + var iTypeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), "IType"); + var inullableBasicTypeInterface = TypeSpec.interfaceBuilder("INullableBasicType"); + inullableBasicTypeInterface.addModifiers(Modifier.PUBLIC); + inullableBasicTypeInterface.addSuperinterface(iTypeInterfaceType); + try { + writeClass(outPath, joinPackage(versionPackage, "data.nullables"), inullableBasicTypeInterface); + } 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 typeInterfaceType = ClassName.get(joinPackage(versionPackage, "data"), type); + var typeInterface = TypeSpec.interfaceBuilder(type); + typeInterface.addModifiers(Modifier.PUBLIC); + typeInterface.addSuperinterface(iTypeInterfaceType); + var getMetaTypeMethod = MethodSpec + .methodBuilder("getMetaId$" + type) + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .returns(TypeName.INT); + typeInterface.addMethod(getMetaTypeMethod.build()); + var getBasicTypeMethod = MethodSpec + .methodBuilder("getBasicType$") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .returns(ClassName.get(joinPackage(basePackageName, ""), "BasicType")); + typeInterface.addMethod(getBasicTypeMethod.build()); + + // If it's the latest version, add the common methods + if (nextVersion.isEmpty()) { + var interfaceDataConfiguration = configuration.interfacesData.get(type); + if (interfaceDataConfiguration != null) { + // Extend this interface + for (String extendedInterface : interfaceDataConfiguration.extendInterfaces) { + typeInterface.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data"), extendedInterface)); + } + Map commonFields = new HashMap<>(); + for (Entry e : interfaceDataConfiguration.commonData.entrySet()) { + String key = e.getKey(); + String value = e.getValue(); + commonFields.put(key, new CommonField(key, value, true)); + } + for (Entry entry : interfaceDataConfiguration.commonGetters.entrySet()) { + String field = entry.getKey(); + String fieldType = entry.getValue(); + commonFields.put(field, new CommonField(field, fieldType, false)); + } + for (CommonField fieldInfo : commonFields.values()) { + var fieldTypeType = fieldInfo.fieldType.equals("?") ? ClassName.get("java.lang", "Object") + : typeTypes.get(fieldInfo.fieldType); + // Add common data getter + { + var getterMethod = MethodSpec + .methodBuilder("get" + capitalize(fieldInfo.fieldName)) + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .returns(fieldTypeType); + if (!fieldTypeType.isPrimitive()) { + getterMethod.addAnnotation(NotNull.class); + getterMethod.addAnnotation(NonNull.class); + } + typeInterface.addMethod(getterMethod.build()); + } + + // Add common data setter + if (fieldInfo.hasSetter) { + if (!fieldInfo.fieldType.equals("?")) { + var param = ParameterSpec.builder(fieldTypeType, fieldInfo.fieldName, Modifier.FINAL); + if (!fieldTypeType.isPrimitive()) { + param.addAnnotation(NotNull.class); + param.addAnnotation(NonNull.class); + } + var setterMethod = MethodSpec + .methodBuilder("set" + capitalize(fieldInfo.fieldName)) + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.ABSTRACT) + .addParameter(param.build()) + .returns(typeInterfaceType) + .addAnnotation(NotNull.class) + .addAnnotation(NonNull.class); + typeInterface.addMethod(setterMethod.build()); + } + } + } + } + } + + try { + writeClass(outPath, joinPackage(versionPackage, "data"), typeInterface); + } catch (IOException e) { + throw new IOError(e); + } + } + } + + // Create the basic types classes + { + for (Entry stringClassConfigurationEntry : versionConfiguration.classes.entrySet()) { + String type = stringClassConfigurationEntry.getKey(); + ClassConfiguration classConfiguration = stringClassConfigurationEntry.getValue(); + var typeClass = TypeSpec.classBuilder(type); + typeClass.addModifiers(Modifier.PUBLIC); + typeClass.addModifiers(Modifier.FINAL); + typeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data"), "IBasicType")); + typeClass.addAnnotation(EqualsAndHashCode.class); + typeClass.addAnnotation(AnnotationSpec + .builder(AllArgsConstructor.class) + .addMember("staticName", "$S", "of") + .build()); + typeClass.addAnnotation(lombok.Builder.class); + typeClass.addAnnotation(ToString.class); + var constructor = MethodSpec.constructorBuilder(); + constructor.addModifiers(Modifier.PUBLIC); + var getBasicTypeMethod = MethodSpec + .methodBuilder("getBasicType$") + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL) + .addAnnotation(Override.class) + .returns(ClassName.get(joinPackage(basePackageName, ""), "BasicType")) + .addStatement("return $T." + type, ClassName.get(joinPackage(basePackageName, ""), "BasicType")); + typeClass.addMethod(getBasicTypeMethod.build()); + var superTypes = versionConfiguration.superTypes + .entrySet() + .parallelStream() + .filter(((entry) -> entry.getValue().contains(type))) + .map((entry) -> Map.entry(entry.getKey(), indexOf(entry.getValue(), type))) + .collect(Collectors.toUnmodifiableSet()); + for (Entry superType : superTypes) { + typeClass.addSuperinterface(ClassName.get(joinPackage(versionPackage, "data"), superType.getKey())); + + var getMetaIdTypeField = FieldSpec + .builder(TypeName.INT, "META_ID$" + capitalizeAll(superType.getKey())) + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.STATIC) + .addModifiers(Modifier.FINAL) + .initializer("" + superType.getValue()); + typeClass.addField(getMetaIdTypeField.build()); + + var getMetaTypeMethod = MethodSpec + .methodBuilder("getMetaId$" + superType.getKey()) + .addModifiers(Modifier.PUBLIC) + .addModifiers(Modifier.FINAL) + .addAnnotation(Override.class) + .returns(TypeName.INT) + .addStatement("return this." + "META_ID$$" + capitalizeAll(superType.getKey())); + typeClass.addMethod(getMetaTypeMethod.build()); + } + for (Entry stringStringEntry : classConfiguration.getData().entrySet()) { + String key = stringStringEntry.getKey(); + String value = stringStringEntry.getValue(); + var isGetterOverride = false; + var isSetterOverride = false; + if (nextVersion.isEmpty()) { + for (Entry superType : superTypes) { + if (superType != null) { + var interfaceCommonDataConfiguration = configuration.interfacesData.getOrDefault(superType.getKey(), + null + ); + if (interfaceCommonDataConfiguration != null + && interfaceCommonDataConfiguration.commonData.containsKey(key) + && !interfaceCommonDataConfiguration.commonData.get(key).equals("?")) { + isGetterOverride = true; + isSetterOverride = true; + } + if (interfaceCommonDataConfiguration != null + && interfaceCommonDataConfiguration.commonGetters.containsKey(key) + && !interfaceCommonDataConfiguration.commonGetters.get(key).equals("?")) { + isGetterOverride = true; + } + } + } + } + + addField(typeClass, constructor, key, typeTypes.get(value), true, true, false, isGetterOverride); + addImmutableSetter(typeClass, + ClassName.get(joinPackage(versionPackage, "data"), type), + classConfiguration.getData().keySet(), + key, + typeTypes.get(value), + isSetterOverride + ); + } + // Add string representer only if this object is at the current version, the old data don't need a string representation... + if (nextVersion.isEmpty() && classConfiguration.getStringRepresenter() != null) { + typeClass.addMethod(MethodSpec + .methodBuilder("toString") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(String.class) + .addStatement("return " + classConfiguration.getStringRepresenter() + "(this)") + .build()); + } + //todo: now we use lombok to generate the constructor. + //typeClass.addMethod(constructor.build()); + + var mapConstructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC); + mapConstructor.addException(IllegalStateException.class); + mapConstructor.addParameter(ParameterizedTypeName.get(Map.class, String.class, Object.class), "fields"); + mapConstructor.addStatement( + "if (fields.size() != " + classConfiguration.getData().size() + ") throw new $T()", + IllegalStateException.class + ); + for (Entry entry : classConfiguration.getData().entrySet()) { + String field = entry.getKey(); + String fieldType = entry.getValue(); + var fieldTypeType = typeTypes.get(fieldType); + mapConstructor.addStatement("if (!fields.containsKey(\"" + field + "\")) throw new $T()", + IllegalStateException.class + ); + boolean requiresNotNull = !fieldTypeType.isPrimitive(); + var assignMethodStatementBuilder = CodeBlock.builder().add("this." + field + " = "); + if (requiresNotNull) { + assignMethodStatementBuilder.add("$T.requireNonNull(", Objects.class); + } + assignMethodStatementBuilder.add("($T) fields.get(\"" + field + "\")", typeTypes.get(fieldType)); + if (requiresNotNull) { + assignMethodStatementBuilder.add(")"); + } + mapConstructor.addStatement(assignMethodStatementBuilder.build()); + } + typeClass.addMethod(mapConstructor.build()); + + try { + writeClass(outPath, joinPackage(versionPackage, "data"), typeClass); + } catch (IOException e) { + throw new IOError(e); + } + } + } + + // Create an upgrader + + } + } + + private void registerArrayType(String versionPackage, + ClassName versionClassType, + HashMap typeOptionalSerializers, + HashMap typeSerializeStatement, + HashMap typeDeserializeStatement, + HashMap typeMustGenerateSerializer, + String type) { + typeOptionalSerializers.put("§" + type, + ClassName.get(joinPackage(versionPackage, "serializers"), "Array" + type + "Serializer")); + typeSerializeStatement.put("§" + type, new SerializeCodeBlockGenerator( + CodeBlock.builder().add("$T.Array" + type + "SerializerInstance.serialize(dataOutput, ", versionClassType) + .build(), CodeBlock.builder().add(")").build())); + typeDeserializeStatement.put("§" + type, + CodeBlock.builder().add("$T.Array" + type + "SerializerInstance.deserialize(dataInput)", versionClassType) + .build()); + typeMustGenerateSerializer.put("§" + type, true); + } + + private MethodSpec.Builder createEmptySerializeMethod(TypeName classType) { + var serializeMethod = MethodSpec.methodBuilder("serialize"); + serializeMethod.addAnnotation(Override.class); + serializeMethod.addModifiers(Modifier.PUBLIC); + serializeMethod.addModifiers(Modifier.FINAL); + serializeMethod.returns(TypeName.VOID); + serializeMethod.addParameter(ParameterSpec.builder(DataOutput.class, "dataOutput").build()); + serializeMethod + .addParameter(ParameterSpec.builder(classType, "data").addAnnotation(NotNull.class).addAnnotation(NonNull.class).build()); + serializeMethod.addException(IOException.class); + serializeMethod.addStatement("$T.requireNonNull(data)", Objects.class); + return serializeMethod; + } + + private MethodSpec.Builder createEmptyDeserializeMethod(TypeName classType) { + var deserializeMethod = MethodSpec.methodBuilder("deserialize"); + deserializeMethod.addAnnotation(Override.class); + deserializeMethod.addAnnotation(NotNull.class); + deserializeMethod.addAnnotation(NonNull.class); + deserializeMethod.addModifiers(Modifier.PUBLIC); + deserializeMethod.addModifiers(Modifier.FINAL); + deserializeMethod.returns(classType); + deserializeMethod.addParameter(ParameterSpec.builder(DataInput.class, "dataInput").build()); + deserializeMethod.addException(IOException.class); + return deserializeMethod; + } + + @Value + public static class NeededTypes { + boolean nullableTypeNeeded; + boolean nextVersionNullableTypeNeeded; + boolean arrayTypeNeeded; + boolean nextVersionArrayTypeNeeded; + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public NeededTypes registerNeededTypes(VersionConfiguration versionConfiguration, + String type, + Optional nextVersion, + Optional nextVersionPackage, + ClassName versionClassType, + String versionPackage, + HashMap typeOptionalSerializers, + HashMap typeSerializeStatement, + HashMap typeDeserializeStatement, + HashMap typeMustGenerateSerializer, + HashMap typeTypes, @Nullable HashMap nextVersionTypeTypes, + Supplier arrayClassName, + Supplier nextArrayClassName) { + // Check if the nullable type is needed + boolean nullableTypeNeeded = versionConfiguration.classes + .values() + .parallelStream() + .map(ClassConfiguration::getData) + .map(Map::values) + .flatMap(Collection::parallelStream) + .filter((typeZ) -> typeZ.startsWith("-")) + .map((typeZ) -> typeZ.substring(1)) + .anyMatch((typeZ) -> typeZ.equals(type)); + + boolean nextVersionNullableTypeNeeded = nextVersion + .filter(s -> configuration.versions.get(s).classes + .values() + .parallelStream() + .map(ClassConfiguration::getData) + .map(Map::values) + .flatMap(Collection::parallelStream) + .filter((typeZ) -> typeZ.startsWith("-")) + .map((typeZ) -> typeZ.substring(1)) + .anyMatch((typeZ) -> typeZ.equals(type))) + .isPresent(); + + + if (nullableTypeNeeded) { + typeOptionalSerializers.put("-" + type, + ClassName.get(joinPackage(versionPackage, "serializers"), "Nullable" + type + "Serializer")); + typeSerializeStatement.put("-" + type, new SerializeCodeBlockGenerator(CodeBlock.builder() + .add("$T.Nullable" + type + "SerializerInstance.serialize(dataOutput, ", versionClassType).build(), + CodeBlock.builder().add(")").build())); + typeDeserializeStatement.put("-" + type, CodeBlock.builder() + .add("$T.Nullable" + type + "SerializerInstance.deserialize(dataInput)", versionClassType).build()); + typeMustGenerateSerializer.put("-" + type, true); + typeTypes.put("-" + type, ClassName.get(joinPackage(versionPackage, "data.nullables"), "Nullable" + type)); + } + if (nextVersionNullableTypeNeeded) { + assert nextVersionTypeTypes != null; + nextVersionTypeTypes.put("-" + type, ClassName.get(joinPackage(nextVersionPackage.orElseThrow(), "data.nullables"), "Nullable" + type)); + } + + // Check if the array type is needed + boolean arrayTypeNeeded = versionConfiguration.classes + .values() + .parallelStream() + .map(ClassConfiguration::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 + .values() + .parallelStream() + .map(ClassConfiguration::getData) + .map(Map::values) + .flatMap(Collection::parallelStream) + .filter((typeZ) -> typeZ.startsWith("§")) + .map((typeZ) -> typeZ.substring(1)) + .anyMatch((typeZ) -> typeZ.equals(type)) + ).isPresent(); + + if (arrayTypeNeeded) { + registerArrayType(versionPackage, + versionClassType, + typeOptionalSerializers, + typeSerializeStatement, + typeDeserializeStatement, + typeMustGenerateSerializer, + type + ); + typeTypes.put("§" + type, ArrayTypeName.of(arrayClassName.get())); + } + if (nextVersionArrayTypeNeeded) { + assert nextVersionTypeTypes != null; + nextVersionTypeTypes.put("§" + type, ArrayTypeName.of(nextArrayClassName.get())); + } + + return new NeededTypes(nullableTypeNeeded, + nextVersionNullableTypeNeeded, + arrayTypeNeeded, + nextVersionArrayTypeNeeded + ); + } + + private String capitalizeAll(String text) { + StringBuilder sb = new StringBuilder(); + boolean firstChar = true; + for (char c : text.toCharArray()) { + if (Character.isUpperCase(c) && !firstChar) { + sb.append('_'); + sb.append(c); + } else { + sb.append(Character.toUpperCase(c)); + } + firstChar = false; + } + 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)); + setterMethod.addModifiers(Modifier.PUBLIC); + setterMethod.addModifiers(Modifier.FINAL); + setterMethod.addAnnotation(NotNull.class); + setterMethod.addAnnotation(NonNull.class); + var param = ParameterSpec.builder(fieldType, fieldName, Modifier.FINAL); + if (!fieldType.isPrimitive()) { + param.addAnnotation(NotNull.class); + param.addAnnotation(NonNull.class); + } + if (isOverride) { + setterMethod.addAnnotation(Override.class); + } + setterMethod.addParameter(param.build()); + setterMethod.returns(classType); + if (!fieldType.isPrimitive()) { + setterMethod.addStatement("$T.requireNonNull(" + fieldName + ")", Objects.class); + } + setterMethod.addCode("$[return $T.of(\n$]", classType); + setterMethod.addCode("$>"); + AtomicInteger i = new AtomicInteger(fieldNames.size()); + for (String otherFieldName : fieldNames) { + boolean isLast = i.decrementAndGet() == 0; + setterMethod.addCode(otherFieldName).addCode((isLast ? "" : ",") + "\n"); + } + setterMethod.addCode("$<"); + setterMethod.addStatement(")"); + classBuilder.addMethod(setterMethod.build()); + } + + private void addField(Builder classBuilder, @Nullable MethodSpec.Builder constructorBuilder, String fieldName, + TypeName fieldType, boolean isFinal, boolean hasGetter, boolean hasSetter, boolean isOverride) { + if (isFinal && constructorBuilder == null) { + throw new IllegalStateException(); + } + if (isFinal && hasSetter) { + throw new IllegalStateException(); + } + if (hasSetter) { + throw new UnsupportedOperationException(); + } + var field = FieldSpec.builder(fieldType, fieldName, Modifier.PRIVATE); + if (!fieldType.isPrimitive()) { + field.addAnnotation(NotNull.class); + field.addAnnotation(NonNull.class); + } + if (isFinal) { + field.addModifiers(Modifier.FINAL); + } + classBuilder.addField(field.build()); + if (hasGetter) { + var getter = MethodSpec.methodBuilder("get" + capitalize(fieldName)); + getter.addModifiers(Modifier.PUBLIC); + getter.addModifiers(Modifier.FINAL); + if (!fieldType.isPrimitive()) { + getter.addAnnotation(NotNull.class); + getter.addAnnotation(NonNull.class); + } + if (isOverride) { + getter.addAnnotation(Override.class); + } + getter.returns(fieldType); + getter.addStatement("return this." + fieldName); + classBuilder.addMethod(getter.build()); + } + if (constructorBuilder != null) { + var param = ParameterSpec.builder(fieldType, fieldName, Modifier.FINAL); + boolean requiresNotNull = !fieldType.isPrimitive(); + if (requiresNotNull) { + param.addAnnotation(NotNull.class); + param.addAnnotation(NonNull.class); + } + constructorBuilder.addParameter(param.build()); + + var assignStatementBuilder = CodeBlock + .builder() + .add("this." + fieldName + " = "); + if (requiresNotNull) { + assignStatementBuilder.add("$T.requireNonNull(", Objects.class); + } + assignStatementBuilder.add(fieldName); + if (requiresNotNull) { + assignStatementBuilder.add(")"); + } + + constructorBuilder.addStatement(assignStatementBuilder.build()); + } + } + + private int indexOf(Set value, String type) { + if (!value.contains(type)) { + return -1; + } + int i = 0; + for (String s : value) { + if (type.equals(s)) { + break; + } + i++; + } + return i; + } + + private String capitalize(String field) { + 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)); + } + } + + private String joinPackage(String basePackageName, String packageName) { + if (basePackageName.isBlank()) { + basePackageName = "org.generated"; + } + if (packageName.isBlank()) { + return basePackageName; + } else { + return basePackageName + "." + packageName; + } + } + + private void writeClass(Path outPath, String classPackage, Builder versionsClass) throws IOException { + JavaFile.builder(classPackage, versionsClass.build()).build().writeTo(outPath); + } + + private String getVersionVarName(String version) { + return "V" + version.replace('-', '_').replace('.', '_'); + } + + 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; + } +} diff --git a/src/main/java/it/cavallium/data/generator/SourcesGeneratorConfiguration.java b/src/main/java/it/cavallium/data/generator/SourcesGeneratorConfiguration.java new file mode 100644 index 0000000..2d0b07c --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/SourcesGeneratorConfiguration.java @@ -0,0 +1,178 @@ +package it.cavallium.data.generator; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class SourcesGeneratorConfiguration { + public String currentVersion; + public Map interfacesData; + public Map versions; + + public static class InterfaceDataConfiguration { + public Set extendInterfaces = new HashSet<>(); + public Map commonData = new HashMap<>(); + public Map commonGetters = new HashMap<>(); + } + + public static class VersionConfiguration { + public DetailsConfiguration details; + public Map> superTypes; + public Map customTypes; + public Map classes; + public List transformations; + } + + + public static class DetailsConfiguration { + public String changelog; + } + + public static class ClassConfiguration { + public String stringRepresenter; + + public LinkedHashMap data; + + public String getStringRepresenter() { + return stringRepresenter; + } + + public LinkedHashMap getData() { + return data; + } + } + + public static class VersionTransformation { + public MoveDataConfiguration moveData = null; + public RemoveDataConfiguration removeData = null; + public UpgradeDataConfiguration upgradeData = null; + public NewDataConfiguration newData = null; + + void checkConsistency() { + int nonNullValues = 0; + if (moveData != null) nonNullValues++; + if (removeData != null) nonNullValues++; + if (upgradeData != null) nonNullValues++; + if (newData != null) nonNullValues++; + if (nonNullValues != 1) { + throw new IllegalArgumentException("Please fill only one transformation!"); + } + } + + public boolean isForClass(String type) { + checkConsistency(); + if (moveData != null) { + return moveData.transformClass.equals(type); + } + if (removeData != null) { + return removeData.transformClass.equals(type); + } + if (upgradeData != null) { + return upgradeData.transformClass.equals(type); + } + if (newData != null) { + return newData.transformClass.equals(type); + } + throw new IllegalStateException(); + } + + public TransformationConfiguration getTransformation() { + checkConsistency(); + if (moveData != null) { + return moveData; + } + if (removeData != null) { + return removeData; + } + if (upgradeData != null) { + return upgradeData; + } + if (newData != null) { + return newData; + } + throw new IllegalStateException(); + } + } + + + public interface TransformationConfiguration { + String getTransformClass(); + + String getTransformName(); + } + + public static class MoveDataConfiguration implements TransformationConfiguration { + + public String transformClass; + public String from; + public String to; + + @Override + public String getTransformClass() { + return transformClass; + } + + @Override + public String getTransformName() { + return "move-data"; + } + } + + public static class RemoveDataConfiguration implements TransformationConfiguration { + + public String transformClass; + public String from; + + @Override + public String getTransformClass() { + return transformClass; + } + + @Override + public String getTransformName() { + return "remove-data"; + } + } + + public static class UpgradeDataConfiguration implements TransformationConfiguration { + + public String transformClass; + public String from; + public String upgrader; + + @Override + public String getTransformClass() { + return transformClass; + } + + @Override + public String getTransformName() { + return "upgrade-data"; + } + } + + public static class NewDataConfiguration implements TransformationConfiguration { + + public String transformClass; + public String to; + public String initializer; + + @Override + public String getTransformClass() { + return transformClass; + } + + @Override + public String getTransformName() { + return "new-data"; + } + } + + public static class CustomTypesConfiguration { + public String javaClass; + public String serializer; + } +} diff --git a/src/main/java/it/cavallium/data/generator/Standalone.java b/src/main/java/it/cavallium/data/generator/Standalone.java new file mode 100644 index 0000000..5c3ab81 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/Standalone.java @@ -0,0 +1,12 @@ +package it.cavallium.data.generator; + +import java.io.IOException; +import java.nio.file.Paths; + +public class Standalone { + + public static void main(String[] args) throws IOException { + SourcesGenerator sourcesGenerator = SourcesGenerator.load(Paths.get(args[0])); + sourcesGenerator.generateSources(args[1], Paths.get(args[2])); + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/ArrayStringSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/ArrayStringSerializer.java new file mode 100644 index 0000000..252438f --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/ArrayStringSerializer.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class ArrayStringSerializer implements DataSerializer { + + @Override + public void serialize(DataOutput dataOutput, @NotNull String[] data) throws IOException { + dataOutput.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + dataOutput.writeUTF(data[i]); + } + } + + @NotNull + @Override + public String[] deserialize(DataInput dataInput) throws IOException { + var data = new String[dataInput.readInt()]; + for (int i = 0; i < data.length; i++) { + data[i] = dataInput.readUTF(); + } + return data; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/ArraybooleanSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/ArraybooleanSerializer.java new file mode 100644 index 0000000..7405756 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/ArraybooleanSerializer.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class ArraybooleanSerializer implements DataSerializer { + + @Override + public void serialize(DataOutput dataOutput, @NotNull boolean[] data) throws IOException { + dataOutput.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + dataOutput.writeBoolean(data[i]); + } + } + + @NotNull + @Override + public boolean[] deserialize(DataInput dataInput) throws IOException { + var data = new boolean[dataInput.readInt()]; + for (int i = 0; i < data.length; i++) { + data[i] = dataInput.readBoolean(); + } + return data; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/ArraybyteSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/ArraybyteSerializer.java new file mode 100644 index 0000000..0ceeb25 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/ArraybyteSerializer.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class ArraybyteSerializer implements DataSerializer { + + @Override + public void serialize(DataOutput dataOutput, @NotNull byte[] data) throws IOException { + dataOutput.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + dataOutput.writeByte(data[i]); + } + } + + @NotNull + @Override + public byte[] deserialize(DataInput dataInput) throws IOException { + var data = new byte[dataInput.readInt()]; + for (int i = 0; i < data.length; i++) { + data[i] = dataInput.readByte(); + } + return data; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/ArraycharSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/ArraycharSerializer.java new file mode 100644 index 0000000..7efae93 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/ArraycharSerializer.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class ArraycharSerializer implements DataSerializer { + + @Override + public void serialize(DataOutput dataOutput, @NotNull char[] data) throws IOException { + dataOutput.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + dataOutput.writeChar(data[i]); + } + } + + @NotNull + @Override + public char[] deserialize(DataInput dataInput) throws IOException { + var data = new char[dataInput.readInt()]; + for (int i = 0; i < data.length; i++) { + data[i] = dataInput.readChar(); + } + return data; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/ArraydoubleSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/ArraydoubleSerializer.java new file mode 100644 index 0000000..83a79fb --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/ArraydoubleSerializer.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class ArraydoubleSerializer implements DataSerializer { + + @Override + public void serialize(DataOutput dataOutput, @NotNull double[] data) throws IOException { + dataOutput.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + dataOutput.writeDouble(data[i]); + } + } + + @NotNull + @Override + public double[] deserialize(DataInput dataInput) throws IOException { + var data = new double[dataInput.readInt()]; + for (int i = 0; i < data.length; i++) { + data[i] = dataInput.readDouble(); + } + return data; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/ArrayfloatSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/ArrayfloatSerializer.java new file mode 100644 index 0000000..d17b166 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/ArrayfloatSerializer.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class ArrayfloatSerializer implements DataSerializer { + + @Override + public void serialize(DataOutput dataOutput, @NotNull float[] data) throws IOException { + dataOutput.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + dataOutput.writeFloat(data[i]); + } + } + + @NotNull + @Override + public float[] deserialize(DataInput dataInput) throws IOException { + var data = new float[dataInput.readInt()]; + for (int i = 0; i < data.length; i++) { + data[i] = dataInput.readFloat(); + } + return data; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/ArrayintSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/ArrayintSerializer.java new file mode 100644 index 0000000..c3411b3 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/ArrayintSerializer.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class ArrayintSerializer implements DataSerializer { + + @Override + public void serialize(DataOutput dataOutput, @NotNull int[] data) throws IOException { + dataOutput.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + dataOutput.writeInt(data[i]); + } + } + + @NotNull + @Override + public int[] deserialize(DataInput dataInput) throws IOException { + var data = new int[dataInput.readInt()]; + for (int i = 0; i < data.length; i++) { + data[i] = dataInput.readInt(); + } + return data; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/ArraylongSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/ArraylongSerializer.java new file mode 100644 index 0000000..f8671e1 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/ArraylongSerializer.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class ArraylongSerializer implements DataSerializer { + + @Override + public void serialize(DataOutput dataOutput, @NotNull long[] data) throws IOException { + dataOutput.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + dataOutput.writeLong(data[i]); + } + } + + @NotNull + @Override + public long[] deserialize(DataInput dataInput) throws IOException { + var data = new long[dataInput.readInt()]; + for (int i = 0; i < data.length; i++) { + data[i] = dataInput.readLong(); + } + return data; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/ArrayshortSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/ArrayshortSerializer.java new file mode 100644 index 0000000..f6fff19 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/ArrayshortSerializer.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class ArrayshortSerializer implements DataSerializer { + + @Override + public void serialize(DataOutput dataOutput, @NotNull short[] data) throws IOException { + dataOutput.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + dataOutput.writeShort(data[i]); + } + } + + @NotNull + @Override + public short[] deserialize(DataInput dataInput) throws IOException { + var data = new short[dataInput.readInt()]; + for (int i = 0; i < data.length; i++) { + data[i] = dataInput.readShort(); + } + return data; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/IGenericNullable.java b/src/main/java/it/cavallium/data/generator/nativedata/IGenericNullable.java new file mode 100644 index 0000000..e36a670 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/IGenericNullable.java @@ -0,0 +1,5 @@ +package it.cavallium.data.generator.nativedata; + +public interface IGenericNullable { + Object $getNullable(); +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullableString.java b/src/main/java/it/cavallium/data/generator/nativedata/NullableString.java new file mode 100644 index 0000000..6dde480 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullableString.java @@ -0,0 +1,117 @@ +package it.cavallium.data.generator.nativedata; + +import java.io.Serializable; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class NullableString implements Serializable, IGenericNullable { + + private static final long serialVersionUID = 1L; + + private final String value; + + public NullableString(String value) { + this.value = value; + } + + public static NullableString of(@NotNull String value) { + if (value == null) { + throw new NullPointerException(); + } else { + return new NullableString(value); + } + } + + public static NullableString ofNullable(@Nullable String value) { + return new NullableString(value); + } + + public static NullableString ofNullableBlank(@Nullable String value) { + if (value == null || value.isBlank()) { + return empty(); + } + return new NullableString(value); + } + + public static NullableString empty() { + return new NullableString(null); + } + + public boolean isEmpty() { + return value == null; + } + + public boolean isPresent() { + return value != null; + } + + public boolean isContentful() { + return value != null && !value.isBlank(); + } + + @NotNull + public String get() { + if (value == null) { + throw new NullPointerException(); + } else { + return value; + } + } + + public String orElse(String defaultValue) { + if (value == null) { + return defaultValue; + } else { + return value; + } + } + + @Override + public Object $getNullable() { + return this.getNullable(); + } + + @Nullable + public String getNullable() { + return value; + } + + @NotNull + public String getNullable(String defaultValue) { + return value == null ? defaultValue : value; + } + + @NotNull + @Override + public NullableString clone() { + if (value != null) { + return NullableString.of(value); + } else { + return NullableString.empty(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (NullableString) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + if (value == null) return "null"; + return "" + value; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullableStringSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/NullableStringSerializer.java new file mode 100644 index 0000000..6bcc6cd --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullableStringSerializer.java @@ -0,0 +1,34 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class NullableStringSerializer implements DataSerializer { + + public static final NullableStringSerializer INSTANCE = new NullableStringSerializer(); + + @Override + public void serialize(DataOutput dataOutput, @NotNull NullableString data) throws IOException { + if (data.isEmpty()) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + String dataContent = data.get(); + dataOutput.writeUTF(dataContent); + } + } + + @NotNull + @Override + public NullableString deserialize(DataInput dataInput) throws IOException { + var isPresent = dataInput.readBoolean(); + if (!isPresent) { + return NullableString.empty(); + } else { + return NullableString.of(dataInput.readUTF()); + } + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullableboolean.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullableboolean.java new file mode 100644 index 0000000..a8c6bca --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullableboolean.java @@ -0,0 +1,101 @@ +package it.cavallium.data.generator.nativedata; + +import java.io.Serializable; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Nullableboolean implements Serializable, IGenericNullable { + + private static final long serialVersionUID = 1L; + + private final Boolean value; + + public Nullableboolean(Boolean value) { + this.value = value; + } + + public static Nullableboolean of(boolean value) { + return new Nullableboolean(value); + } + + public static Nullableboolean ofNullable(@Nullable Boolean value) { + return new Nullableboolean(value); + } + + public static Nullableboolean empty() { + return new Nullableboolean(null); + } + + public boolean isEmpty() { + return value == null; + } + + public boolean isPresent() { + return value != null; + } + + public boolean get() { + if (value == null) { + throw new NullPointerException(); + } else { + return value; + } + } + + public boolean orElse(boolean defaultValue) { + if (value == null) { + return defaultValue; + } else { + return value; + } + } + + @Override + public Object $getNullable() { + return this.getNullable(); + } + + @Nullable + public Boolean getNullable() { + return value; + } + + public boolean getNullable(boolean defaultValue) { + return value == null ? defaultValue : value; + } + + @NotNull + @Override + public Nullableboolean clone() { + if (value != null) { + return Nullableboolean.of(value); + } else { + return Nullableboolean.empty(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Nullableboolean that = (Nullableboolean) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + if (value == null) return "null"; + return "" + value; + } + +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullablebooleanSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/NullablebooleanSerializer.java new file mode 100644 index 0000000..dad1d70 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullablebooleanSerializer.java @@ -0,0 +1,34 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class NullablebooleanSerializer implements DataSerializer { + + public static final NullablebooleanSerializer INSTANCE = new NullablebooleanSerializer(); + + @Override + public void serialize(DataOutput dataOutput, @NotNull Nullableboolean data) throws IOException { + if (data.isEmpty()) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + boolean dataContent = data.get(); + dataOutput.writeBoolean(dataContent); + } + } + + @NotNull + @Override + public Nullableboolean deserialize(DataInput dataInput) throws IOException { + var isPresent = dataInput.readBoolean(); + if (!isPresent) { + return Nullableboolean.empty(); + } else { + return Nullableboolean.of(dataInput.readBoolean()); + } + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullablebyte.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullablebyte.java new file mode 100644 index 0000000..0186d59 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullablebyte.java @@ -0,0 +1,104 @@ +package it.cavallium.data.generator.nativedata; + +import java.io.Serializable; +import java.util.Objects; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@EqualsAndHashCode +@ToString +public class Nullablebyte implements Serializable, IGenericNullable { + + private static final long serialVersionUID = 1L; + + private final Byte value; + + public Nullablebyte(Byte value) { + this.value = value; + } + + public static Nullablebyte of(byte value) { + return new Nullablebyte(value); + } + + public static Nullablebyte ofNullable(@Nullable Byte value) { + return new Nullablebyte(value); + } + + public static Nullablebyte empty() { + return new Nullablebyte(null); + } + + public boolean isEmpty() { + return value == null; + } + + public boolean isPresent() { + return value != null; + } + + public byte get() { + if (value == null) { + throw new NullPointerException(); + } else { + return value; + } + } + + public byte orElse(byte defaultValue) { + if (value == null) { + return defaultValue; + } else { + return value; + } + } + + @Override + public Object $getNullable() { + return this.getNullable(); + } + + @Nullable + public Byte getNullable() { + return value; + } + + public byte getNullable(byte defaultValue) { + return value == null ? defaultValue : value; + } + + @NotNull + @Override + public Nullablebyte clone() { + if (value != null) { + return Nullablebyte.of(value); + } else { + return Nullablebyte.empty(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Nullablebyte that = (Nullablebyte) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + if (value == null) return "null"; + return "" + value; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullablebyteSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/NullablebyteSerializer.java new file mode 100644 index 0000000..027e5de --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullablebyteSerializer.java @@ -0,0 +1,34 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class NullablebyteSerializer implements DataSerializer { + + public static final NullablebyteSerializer INSTANCE = new NullablebyteSerializer(); + + @Override + public void serialize(DataOutput dataOutput, @NotNull Nullablebyte data) throws IOException { + if (data.isEmpty()) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + byte dataContent = data.get(); + dataOutput.writeByte(dataContent); + } + } + + @NotNull + @Override + public Nullablebyte deserialize(DataInput dataInput) throws IOException { + var isPresent = dataInput.readBoolean(); + if (!isPresent) { + return Nullablebyte.empty(); + } else { + return Nullablebyte.of(dataInput.readByte()); + } + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullablechar.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullablechar.java new file mode 100644 index 0000000..c39564b --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullablechar.java @@ -0,0 +1,104 @@ +package it.cavallium.data.generator.nativedata; + +import java.io.Serializable; +import java.util.Objects; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@EqualsAndHashCode +@ToString +public class Nullablechar implements Serializable, IGenericNullable { + + private static final long serialVersionUID = 1L; + + private final Character value; + + public Nullablechar(Character value) { + this.value = value; + } + + public static Nullablechar of(char value) { + return new Nullablechar(value); + } + + public static Nullablechar ofNullable(@Nullable Character value) { + return new Nullablechar(value); + } + + public static Nullablechar empty() { + return new Nullablechar(null); + } + + public boolean isEmpty() { + return value == null; + } + + public boolean isPresent() { + return value != null; + } + + public char get() { + if (value == null) { + throw new NullPointerException(); + } else { + return value; + } + } + + public char orElse(char defaultValue) { + if (value == null) { + return defaultValue; + } else { + return value; + } + } + + @Override + public Object $getNullable() { + return this.getNullable(); + } + + @Nullable + public Character getNullable() { + return value; + } + + public char getNullable(char defaultValue) { + return value == null ? defaultValue : value; + } + + @NotNull + @Override + public Nullablechar clone() { + if (value != null) { + return Nullablechar.of(value); + } else { + return Nullablechar.empty(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (Nullablechar) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + if (value == null) return "null"; + return "" + value; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullablecharSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/NullablecharSerializer.java new file mode 100644 index 0000000..c9a4d8b --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullablecharSerializer.java @@ -0,0 +1,34 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class NullablecharSerializer implements DataSerializer { + + public static final NullablecharSerializer INSTANCE = new NullablecharSerializer(); + + @Override + public void serialize(DataOutput dataOutput, @NotNull Nullablechar data) throws IOException { + if (data.isEmpty()) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + char dataContent = data.get(); + dataOutput.writeChar(dataContent); + } + } + + @NotNull + @Override + public Nullablechar deserialize(DataInput dataInput) throws IOException { + var isPresent = dataInput.readBoolean(); + if (!isPresent) { + return Nullablechar.empty(); + } else { + return Nullablechar.of(dataInput.readChar()); + } + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullabledouble.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullabledouble.java new file mode 100644 index 0000000..37047bf --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullabledouble.java @@ -0,0 +1,95 @@ +package it.cavallium.data.generator.nativedata; + +import java.io.Serializable; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Nullabledouble implements Serializable { + + private static final long serialVersionUID = 1L; + + private final Double value; + + public Nullabledouble(Double value) { + this.value = value; + } + + public static Nullabledouble of(double value) { + return new Nullabledouble(value); + } + + public static Nullabledouble ofNullable(@Nullable Double value) { + return new Nullabledouble(value); + } + + public static Nullabledouble empty() { + return new Nullabledouble(null); + } + + public boolean isEmpty() { + return value == null; + } + + public boolean isPresent() { + return value != null; + } + + public double get() { + if (value == null) { + throw new NullPointerException(); + } else { + return value; + } + } + + public double orElse(double defaultValue) { + if (value == null) { + return defaultValue; + } else { + return value; + } + } + + @Nullable + public Double getNullable() { + return value; + } + + public double getNullable(double defaultValue) { + return value == null ? defaultValue : value; + } + + @NotNull + @Override + public Nullabledouble clone() { + if (value != null) { + return Nullabledouble.of(value); + } else { + return Nullabledouble.empty(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (Nullabledouble) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + if (value == null) return "null"; + return "" + value; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullabledoubleSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/NullabledoubleSerializer.java new file mode 100644 index 0000000..0904bb5 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullabledoubleSerializer.java @@ -0,0 +1,34 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class NullabledoubleSerializer implements DataSerializer { + + public static final NullabledoubleSerializer INSTANCE = new NullabledoubleSerializer(); + + @Override + public void serialize(DataOutput dataOutput, @NotNull Nullabledouble data) throws IOException { + if (data.isEmpty()) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + double dataContent = data.get(); + dataOutput.writeDouble(dataContent); + } + } + + @NotNull + @Override + public Nullabledouble deserialize(DataInput dataInput) throws IOException { + var isPresent = dataInput.readBoolean(); + if (!isPresent) { + return Nullabledouble.empty(); + } else { + return Nullabledouble.of(dataInput.readDouble()); + } + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullablefloat.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullablefloat.java new file mode 100644 index 0000000..0280065 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullablefloat.java @@ -0,0 +1,100 @@ +package it.cavallium.data.generator.nativedata; + +import java.io.Serializable; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Nullablefloat implements Serializable, IGenericNullable { + + private static final long serialVersionUID = 1L; + + private final Float value; + + public Nullablefloat(Float value) { + this.value = value; + } + + public static Nullablefloat of(float value) { + return new Nullablefloat(value); + } + + public static Nullablefloat ofNullable(@Nullable Float value) { + return new Nullablefloat(value); + } + + public static Nullablefloat empty() { + return new Nullablefloat(null); + } + + public boolean isEmpty() { + return value == null; + } + + public boolean isPresent() { + return value != null; + } + + public float get() { + if (value == null) { + throw new NullPointerException(); + } else { + return value; + } + } + + public float orElse(float defaultValue) { + if (value == null) { + return defaultValue; + } else { + return value; + } + } + + @Override + public Object $getNullable() { + return this.getNullable(); + } + + @Nullable + public Float getNullable() { + return value; + } + + public float getNullable(float defaultValue) { + return value == null ? defaultValue : value; + } + + @NotNull + @Override + public Nullablefloat clone() { + if (value != null) { + return Nullablefloat.of(value); + } else { + return Nullablefloat.empty(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (Nullablefloat) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + if (value == null) return "null"; + return "" + value; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullablefloatSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/NullablefloatSerializer.java new file mode 100644 index 0000000..2d8b150 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullablefloatSerializer.java @@ -0,0 +1,34 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class NullablefloatSerializer implements DataSerializer { + + public static final NullablefloatSerializer INSTANCE = new NullablefloatSerializer(); + + @Override + public void serialize(DataOutput dataOutput, @NotNull Nullablefloat data) throws IOException { + if (data.isEmpty()) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + float dataContent = data.get(); + dataOutput.writeFloat(dataContent); + } + } + + @NotNull + @Override + public Nullablefloat deserialize(DataInput dataInput) throws IOException { + var isPresent = dataInput.readBoolean(); + if (!isPresent) { + return Nullablefloat.empty(); + } else { + return Nullablefloat.of(dataInput.readFloat()); + } + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullableint.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullableint.java new file mode 100644 index 0000000..cabea4d --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullableint.java @@ -0,0 +1,100 @@ +package it.cavallium.data.generator.nativedata; + +import java.io.Serializable; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Nullableint implements Serializable, IGenericNullable { + + private static final long serialVersionUID = 1L; + + private final Integer value; + + public Nullableint(Integer value) { + this.value = value; + } + + public static Nullableint of(int value) { + return new Nullableint(value); + } + + public static Nullableint ofNullable(@Nullable Integer value) { + return new Nullableint(value); + } + + public static Nullableint empty() { + return new Nullableint(null); + } + + public boolean isEmpty() { + return value == null; + } + + public boolean isPresent() { + return value != null; + } + + public int get() { + if (value == null) { + throw new NullPointerException(); + } else { + return value; + } + } + + public int orElse(int defaultValue) { + if (value == null) { + return defaultValue; + } else { + return value; + } + } + + @Override + public Object $getNullable() { + return this.getNullable(); + } + + @Nullable + public Integer getNullable() { + return value; + } + + public int getNullable(int defaultValue) { + return value == null ? defaultValue : value; + } + + @NotNull + @Override + public Nullableint clone() { + if (value != null) { + return Nullableint.of(value); + } else { + return Nullableint.empty(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (Nullableint) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + if (value == null) return "null"; + return "" + value; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullableintSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/NullableintSerializer.java new file mode 100644 index 0000000..f9836db --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullableintSerializer.java @@ -0,0 +1,34 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class NullableintSerializer implements DataSerializer { + + public static final NullableintSerializer INSTANCE = new NullableintSerializer(); + + @Override + public void serialize(DataOutput dataOutput, @NotNull Nullableint data) throws IOException { + if (data.isEmpty()) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + int dataContent = data.get(); + dataOutput.writeInt(dataContent); + } + } + + @NotNull + @Override + public Nullableint deserialize(DataInput dataInput) throws IOException { + var isPresent = dataInput.readBoolean(); + if (!isPresent) { + return Nullableint.empty(); + } else { + return Nullableint.of(dataInput.readInt()); + } + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullablelong.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullablelong.java new file mode 100644 index 0000000..42f3cde --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullablelong.java @@ -0,0 +1,100 @@ +package it.cavallium.data.generator.nativedata; + +import java.io.Serializable; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Nullablelong implements Serializable, IGenericNullable { + + private static final long serialVersionUID = 1L; + + private final Long value; + + public Nullablelong(Long value) { + this.value = value; + } + + public static Nullablelong of(long value) { + return new Nullablelong(value); + } + + public static Nullablelong ofNullable(@Nullable Long value) { + return new Nullablelong(value); + } + + public static Nullablelong empty() { + return new Nullablelong(null); + } + + public boolean isEmpty() { + return value == null; + } + + public boolean isPresent() { + return value != null; + } + + public long get() { + if (value == null) { + throw new NullPointerException(); + } else { + return value; + } + } + + public long orElse(long defaultValue) { + if (value == null) { + return defaultValue; + } else { + return value; + } + } + + @Override + public Object $getNullable() { + return this.getNullable(); + } + + @Nullable + public Long getNullable() { + return value; + } + + public long getNullable(long defaultValue) { + return value == null ? defaultValue : value; + } + + @NotNull + @Override + public Nullablelong clone() { + if (value != null) { + return Nullablelong.of(value); + } else { + return Nullablelong.empty(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (Nullablelong) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + if (value == null) return "null"; + return "" + value; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullablelongSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/NullablelongSerializer.java new file mode 100644 index 0000000..3b380c5 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullablelongSerializer.java @@ -0,0 +1,34 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class NullablelongSerializer implements DataSerializer { + + public static final NullablelongSerializer INSTANCE = new NullablelongSerializer(); + + @Override + public void serialize(DataOutput dataOutput, @NotNull Nullablelong data) throws IOException { + if (data.isEmpty()) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + long dataContent = data.get(); + dataOutput.writeLong(dataContent); + } + } + + @NotNull + @Override + public Nullablelong deserialize(DataInput dataInput) throws IOException { + var isPresent = dataInput.readBoolean(); + if (!isPresent) { + return Nullablelong.empty(); + } else { + return Nullablelong.of(dataInput.readLong()); + } + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/Nullableshort.java b/src/main/java/it/cavallium/data/generator/nativedata/Nullableshort.java new file mode 100644 index 0000000..8b839b4 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/Nullableshort.java @@ -0,0 +1,100 @@ +package it.cavallium.data.generator.nativedata; + +import java.io.Serializable; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Nullableshort implements Serializable, IGenericNullable { + + private static final long serialVersionUID = 1L; + + private final Short value; + + public Nullableshort(Short value) { + this.value = value; + } + + public static Nullableshort of(short value) { + return new Nullableshort(value); + } + + public static Nullableshort ofNullable(@Nullable Short value) { + return new Nullableshort(value); + } + + public static Nullableshort empty() { + return new Nullableshort(null); + } + + public boolean isEmpty() { + return value == null; + } + + public boolean isPresent() { + return value != null; + } + + public short get() { + if (value == null) { + throw new NullPointerException(); + } else { + return value; + } + } + + public short orElse(short defaultValue) { + if (value == null) { + return defaultValue; + } else { + return value; + } + } + + @Override + public Object $getNullable() { + return this.getNullable(); + } + + @Nullable + public Short getNullable() { + return value; + } + + public short getNullable(short defaultValue) { + return value == null ? defaultValue : value; + } + + @NotNull + @Override + public Nullableshort clone() { + if (value != null) { + return Nullableshort.of(value); + } else { + return Nullableshort.empty(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (Nullableshort) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + if (value == null) return "null"; + return "" + value; + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/NullableshortSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/NullableshortSerializer.java new file mode 100644 index 0000000..b091a1a --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/NullableshortSerializer.java @@ -0,0 +1,34 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; + +public class NullableshortSerializer implements DataSerializer { + + public static final NullableshortSerializer INSTANCE = new NullableshortSerializer(); + + @Override + public void serialize(DataOutput dataOutput, @NotNull Nullableshort data) throws IOException { + if (data.isEmpty()) { + dataOutput.writeBoolean(false); + } else { + dataOutput.writeBoolean(true); + short dataContent = data.get(); + dataOutput.writeShort(dataContent); + } + } + + @NotNull + @Override + public Nullableshort deserialize(DataInput dataInput) throws IOException { + var isPresent = dataInput.readBoolean(); + if (!isPresent) { + return Nullableshort.empty(); + } else { + return Nullableshort.of(dataInput.readShort()); + } + } +} diff --git a/src/main/java/it/cavallium/data/generator/nativedata/StringSerializer.java b/src/main/java/it/cavallium/data/generator/nativedata/StringSerializer.java new file mode 100644 index 0000000..8ef7894 --- /dev/null +++ b/src/main/java/it/cavallium/data/generator/nativedata/StringSerializer.java @@ -0,0 +1,28 @@ +package it.cavallium.data.generator.nativedata; + +import it.cavallium.data.generator.DataSerializer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.jetbrains.annotations.NotNull; + +public class StringSerializer implements DataSerializer { + + public static final StringSerializer INSTANCE = new StringSerializer(); + + @Override + public void serialize(DataOutput dataOutput, @NotNull String data) throws IOException { + byte[] bytes = data.getBytes(StandardCharsets.UTF_8); + dataOutput.writeInt(bytes.length); + dataOutput.write(bytes); + } + + @NotNull + @Override + public String deserialize(DataInput dataInput) throws IOException { + byte[] bytes = new byte[dataInput.readInt()]; + dataInput.readFully(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } +}