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);
+ }
+}