diff --git a/.github/workflows/build-gradle.yml b/.github/workflows/build-gradle.yml new file mode 100644 index 0000000..393133a --- /dev/null +++ b/.github/workflows/build-gradle.yml @@ -0,0 +1,34 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + - name: Build with Gradle + uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 + with: + arguments: build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4718ce7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +### IntelliJ IDEA ### +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +.idea/ +*.iml + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2d2e6bf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Proto4j + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..22bd08c --- /dev/null +++ b/README.md @@ -0,0 +1,114 @@ +# Proto4j-Objection + +![Module](https://img.shields.io:/static/v1?label=Module&message=objection&color=9cf) +![Build](https://img.shields.io:/static/v1?label=Build&message=passing&color=green) + +This repository contains the source code for the `Objection` module from `Proto4j`. It is considered to be a development repository where changes can be made and features can be requested. The source code is heavily documented as there will be no external documentation. A simple usage of this module is presented below. + + +### Basic Usage + +--- + +There are two possibilities on how to write code that will be serializable by this module: + +1. Use standard `Java` directives +2. Use pre-defined `Annotations` on declared classes and fields. +further +This small overview tries to show both versions as detailed as possible. Nested serializable types will be automatically detected. Yet, there are some standard types defined to be serialized and de-serialized - they can be found in the following table: + +| Class | Serializable | Serializer-Class | +|------------|--------------|------------------------------------------------| +| String | `true` | `StringSerializer` | +| Number | `true` | located as inner class in `NumberSerializer` | +| Collection | `true` | located as inner class in `SequenceSerializer` | +| Map | `true` | located as inner class in `SequenceSerializer` | +| Array | `true` | located as inner class in `SequenceSerializer` | +| MultiArray | `false` | --- | + +If you use a class that is not included in that table, you can write your own `ObjectSerializer` implementation and integrate that into the serialization process. + +#### UseCase 1: Basic class declaration rules + +```java +// Traditional way with Java directives +class Car implements Serializable { + // All fields excluding compiler-generated, static and transient + // fields are going to be serialized. + private int id; + private String name; + + // Use the transient keyword to prevent a field from being serialized + private transient String ignoredField; +} + +// Annotated way: Use the @Version annotation on a class to set a global +// version. Here, the field address is ignored, because version 0 is used. +@Serialize +class Person { + // You can use a version flag to exclude fields from newer versions + // and make the code backwards compatible. No version points to the + // initial version, which is 0. + private String name; + private Car car; + @Version(1) private String address; + +} +``` + +#### UseCase 2: Serialization process + +```java +public static void main(String[] args) { + // At first, create a new Marshaller instance for the target type. + Marshaller marshaller = Objection.createMarshaller(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + // Secondly, create the instance that will be serialized + Car car = new Car(1, "Mustang"); // constructor (int, String) has to be defined + DataOutput = new DataOutputStream(output); + + // Thirdly, serialize the object + OSharedConfiguration config = marshaller.marshall(car, output); + + // With the returned configuration the output can be de-serialized back + // into a Car instance. + DataInput input = new DataInputStream(new ByteArrayInputStream(output.toByteArray())); + Car car2 = marshaller.unmarshall(input, config); +} +``` + +#### UseCase 3: Custom ObjectSerializer implementations + +```java +// Example class that can read/write the previous defined Car.class which is used +// in the Person class. +class CarSerializer extends BasicObjectSerializer { + @Override + public boolean accept(Class type) { + // return type == Car.class, or even better: + return Car.class.isAssignableFrom(type); + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) + throws IOException { + Car car = (Car) writableObject; + dataOutput.writeInt(car.getId()); + dataOutput.writeInt(car.getName().length()); + dataOutput.writeBytes(car.getName()); + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) + throws IOException { + int id = dataInput.readInt(); + int name_length = dataInput.readInt(); + byte[] name = new byte[name_length]; + + dataInput.readFully(name); + return new Car(id, new String(name)); + } +} + +``` \ No newline at end of file diff --git a/src/main/java/io/github/proto4j/objection/AbstractMarshaller.java b/src/main/java/io/github/proto4j/objection/AbstractMarshaller.java new file mode 100644 index 0000000..f7ddbf6 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/AbstractMarshaller.java @@ -0,0 +1,46 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection; //@date 27.08.2022 + + +public abstract class AbstractMarshaller implements Marshaller { + + private OSharedConfiguration configuration; + + public AbstractMarshaller() { + this(Objection.getDefaultConfiguration()); + } + + // ENHANCEMENT: add nonNull()-check before setting the variable. + public AbstractMarshaller(OSharedConfiguration configuration) {this.configuration = configuration;} + + public OSharedConfiguration getConfiguration() { + return configuration; + } + + public void setConfiguration(OSharedConfiguration configuration) { + this.configuration = configuration; + } +} diff --git a/src/main/java/io/github/proto4j/objection/BasicMarshaller.java b/src/main/java/io/github/proto4j/objection/BasicMarshaller.java new file mode 100644 index 0000000..4f70867 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/BasicMarshaller.java @@ -0,0 +1,94 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection; //@date 27.08.2022 + +import io.github.proto4j.objection.model.OClass; +import io.github.proto4j.objection.model.OField; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidClassException; +import java.lang.reflect.Field; +import java.util.Objects; + +public class BasicMarshaller extends AbstractMarshaller { + + public BasicMarshaller() { + } + + public BasicMarshaller(OSharedConfiguration configuration) { + super(configuration); + } + + /** + * {@inheritDoc} + */ + @Override + public OSharedConfiguration marshall(V value, DataOutput output) throws IOException, ReflectiveOperationException { + Objects.requireNonNull(value); + Objects.requireNonNull(output); + + ObjectSerializer sr = getConfiguration().forType(OClass.class); + if (sr == null) { + throw new InvalidClassException("No OClass serializer specified"); + } + + getConfiguration().addType(value.getClass()); + OClass cls = OClass.klass(value, getConfiguration()); + + Object o = Objects.requireNonNull(cls.getInstance()); + for (OField field : cls.getDeclaredFields()) { + if (field.getValue() == null) { + Field linked = field.getLinkedField(); + linked.setAccessible(true); + field.setValue(linked.get(o)); + } + } + + OSerializationContext ctx = new BasicSerializationContext(cls, null, getConfiguration()); + sr.writeObject(output, cls, ctx); + return getConfiguration(); + } + + /** + * {@inheritDoc} + */ + @Override + public OClass unmarshall(DataInput input, OSharedConfiguration configuration) throws IOException { + Objects.requireNonNull(input); + Objects.requireNonNull(configuration); + + setConfiguration(configuration); + ObjectSerializer sr = getConfiguration().forType(OClass.class); + if (sr == null) { + throw new InvalidClassException("No OClass serializer specified"); + } + + OSerializationContext ctx = new BasicSerializationContext(null, null, getConfiguration()); + //noinspection unchecked + return (OClass) sr.getInstance(OClass.class, input, ctx); + } +} diff --git a/src/main/java/io/github/proto4j/objection/BasicObjectSerializer.java b/src/main/java/io/github/proto4j/objection/BasicObjectSerializer.java new file mode 100644 index 0000000..c490b7c --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/BasicObjectSerializer.java @@ -0,0 +1,48 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection; //@date 25.08.2022 + +import java.io.DataInput; +import java.io.IOException; + +public abstract class BasicObjectSerializer implements ObjectSerializer { + + /** + * {@inheritDoc} + */ + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean accept(Class type) { + return true; + } + +} diff --git a/src/main/java/io/github/proto4j/objection/BasicSerializationContext.java b/src/main/java/io/github/proto4j/objection/BasicSerializationContext.java new file mode 100644 index 0000000..d657e36 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/BasicSerializationContext.java @@ -0,0 +1,65 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection; //@date 26.08.2022 + +import io.github.proto4j.objection.model.OClass; +import io.github.proto4j.objection.model.OField; + +public class BasicSerializationContext implements OSerializationContext { + + private final OClass classInfo; + private final OField reference; + private final OSharedConfiguration config; + + public BasicSerializationContext(OClass classInfo, OField reference, OSharedConfiguration config) { + this.classInfo = classInfo; + this.reference = reference; + this.config = config; + } + + /** + * {@inheritDoc} + */ + @Override + public OClass getClassInfo() { + return classInfo; + } + + /** + * {@inheritDoc} + */ + @Override + public OSharedConfiguration getConfig() { + return config; + } + + /** + * {@inheritDoc} + */ + @Override + public OField getReference() { + return reference; + } +} diff --git a/src/main/java/io/github/proto4j/objection/BasicSharedConfiguration.java b/src/main/java/io/github/proto4j/objection/BasicSharedConfiguration.java new file mode 100644 index 0000000..a08811d --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/BasicSharedConfiguration.java @@ -0,0 +1,81 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection; //@date 26.08.2022 + +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public abstract class BasicSharedConfiguration implements OSharedConfiguration { + + private final ConcurrentMap> registeredClasses = new ConcurrentHashMap<>(); + private final List serializers = new LinkedList<>(); + + /** + * {@inheritDoc} + */ + @Override + public Class forName(String name) { + Objects.requireNonNull(name); + + if (!isRegistered(name)) { + return null; + } + return registeredClasses.get(name); + } + + /** + * {@inheritDoc} + */ + @Override + public ObjectSerializer forType(Class type) { + Objects.requireNonNull(type); + + for (ObjectSerializer serializer : serializers) { + if (serializer.accept(type)) { + return serializer; + } + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRegistered(String name) { + return registeredClasses.containsKey(name); + } + + public ConcurrentMap> getRegisteredClasses() { + return registeredClasses; + } + + public List getSerializers() { + return serializers; + } +} diff --git a/src/main/java/io/github/proto4j/objection/Marshaller.java b/src/main/java/io/github/proto4j/objection/Marshaller.java new file mode 100644 index 0000000..7f3925d --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/Marshaller.java @@ -0,0 +1,99 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection;//@date 27.08.2022 + +import io.github.proto4j.objection.model.OClass; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * The base class for objects that are used to serialize and de-serialize + * specific objects of type '{@code V}'. A {@link BasicMarshaller} can be retrieved + * by calling {@link Objection#createMarshaller()}. + *

+ * An abstract version of this interface is provided through the {@link AbstractMarshaller} + * class. The basic usage of this class should be: + *

+ *     Marshaller m = Objection.createMarshaller(baseConfig);
+ *
+ *     // get changed configuration after writing
+ *     OSharedConfiguration config = m.marshall(value, stream);
+ *     // and use that config when de-serializing
+ *     ? value2 = m.unmarshall(stream, config);
+ * 
+ * + * @param the type of objects to read/write + * @see BasicMarshaller + * @see AbstractMarshaller + */ +public interface Marshaller { + + /** + * Tries to write a binary representation of the given instance of type + * {@code V} to the {@link DataOutput} object. + * + * @param value the type instance + * @param output the destination stream wrapper + * @return a modified version of the initial configuration + * @throws IOException if an error while writing occurs + * @throws ReflectiveOperationException if values could not be fetched dynamically + */ + OSharedConfiguration marshall(V value, DataOutput output) throws IOException, ReflectiveOperationException; + + /** + * Tries to read the binary representation of type {@code V} from the given + * {@link DataInput} object with a {@link OSharedConfiguration}. + * + * @param input the input source + * @param configuration an object containing all registered and usable + * serializers and readable types. + * @return the {@link OClass} wrapper for the loaded instance. + * @throws IOException if an error occurs while reading + */ + // ENHANCEMENT: Try to overload this method in the future, so it can be + // called without the need of providing the configuration instance. + OClass unmarshall(DataInput input, OSharedConfiguration configuration) throws IOException; + + /** + * Alternative version for {@link #unmarshall(DataInput, OSharedConfiguration)}. + * + * @param input the input source + * @param configuration an object containing all registered and usable + * serializers and readable types. + * @return the loaded instance. + * @throws IOException if an error occurs while reading + * @throws ReflectiveOperationException if the values could not be applied + * to the new type instance. + * @throws ClassCastException if the wrong class was de-serialized + */ + default V getInstance(DataInput input, OSharedConfiguration configuration) throws IOException, + ReflectiveOperationException { + OClass cls = unmarshall(input, configuration); + return cls.newInstance(); + } + +} diff --git a/src/main/java/io/github/proto4j/objection/OSerializationContext.java b/src/main/java/io/github/proto4j/objection/OSerializationContext.java new file mode 100644 index 0000000..cadeb28 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/OSerializationContext.java @@ -0,0 +1,63 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection;//@date 26.08.2022 + +import io.github.proto4j.objection.model.OClass; +import io.github.proto4j.objection.model.OField; + +/** + * The base class for all implementations used within the serialization + * process. It simply contains three base attributes in this initial + * implementation: + *
    + *
  • the {@link OClass} wrapper for de-serialization
  • + *
  • the {@link OSharedConfiguration} for basic process handling
  • + *
  • the {@link OField} instance when processing fields
  • + *
+ * + * INFO: The {@link #getReference()} method is currently unused, but was + * implemented in recent versions. + * + * @author MatrixEditor + * @version 0.2.0 + */ +public interface OSerializationContext { + + /** + * @return the wrapped type info. + */ + OClass getClassInfo(); + + /** + * @return the current configuration used to de-/serialize binary data. + */ + OSharedConfiguration getConfig(); + + /** + * @return the linked {@link OField} instance when processing field data. + */ + @SuppressWarnings("unused") + OField getReference(); +} diff --git a/src/main/java/io/github/proto4j/objection/OSharedConfiguration.java b/src/main/java/io/github/proto4j/objection/OSharedConfiguration.java new file mode 100644 index 0000000..2d50117 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/OSharedConfiguration.java @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection; //@date 25.08.2022 + +import java.nio.charset.StandardCharsets; + +/** + * The base class for configuration based serialization. For most implementations + * instances of this class should contain a list of serializers and a map of + * classes that can be read/written to or from any source. + * + * @author MatrixEditor + * @version 0.2.0 + */ +public interface OSharedConfiguration { + + ObjectSerializer forType(Class type); + + Class forName(String name); + + default Class forName(byte[] name) throws ClassNotFoundException { + return forName(new String(name, StandardCharsets.UTF_8)); + } + + boolean isRegistered(String name); + + default boolean isRegistered(byte[] name) { + return isRegistered(new String(name, StandardCharsets.UTF_8)); + } + + void addType(Class cls); + + void addSerializer(ObjectSerializer serializer); +} diff --git a/src/main/java/io/github/proto4j/objection/ObjectSerializer.java b/src/main/java/io/github/proto4j/objection/ObjectSerializer.java new file mode 100644 index 0000000..4a7ee55 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/ObjectSerializer.java @@ -0,0 +1,106 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection; //@date 25.08.2022 + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidClassException; +import java.util.Objects; + +/** + * Used by an {@link Marshaller} object and registered in the {@link OSharedConfiguration} + * instance. Objects of this class may be used to read and write the implemented schema. + * It is recommended to register new serializers for the following types of classes: + *
    + *
  • Raw Objects (Object.class)
  • + *
  • Generic self-implemented types
  • + *
  • Self-implemented classes
  • + *
  • In-Built complex types, for example Class.class
  • + *
+ * + * @author MatrixEditor + * @version 0.2.0 + */ +public interface ObjectSerializer { + + /** + * Writes the given object in a specific binary format to the given + * {@link DataOutput} stream. + * + * @param dataOutput a wrapper for the underlying {@link java.io.OutputStream}. + * @param writableObject the object to be written + * @param ctx a general context object storing informational resources, such + * as the configuration with all registered {@link ObjectSerializer} + * instances. + * @throws IOException if an error occurs while writing + */ + void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) throws IOException; + + /** + * Reads data from the given stream and converts that into a qualified + * object. + * + * @param type the class of the returned instance + * @param dataInput a wrapper for the underlying {@link java.io.InputStream}. + * @param ctx a general context object storing informational resources, such + * as the configuration with all registered {@link ObjectSerializer} + * instances. + * @return an instance of the given type. + * @throws IOException if an error occurs while reading + */ + Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException; + + /** + * Reads data from the given stream and converts that into a qualified + * object of type {@code T}. + * + * @param type the class of the returned instance + * @param dataInput a wrapper for the underlying {@link java.io.InputStream}. + * @param ctx a general context object storing informational resources, such + * as the configuration with all registered {@link ObjectSerializer} + * instances. + * @param the type of the returned instance + * @return an instance of the given type. + * @throws IOException if an error occurs while reading + */ + default T getTypeInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + if (!accept(Objects.requireNonNull(type))) { + throw new InvalidClassException(type.getSimpleName() + " is not supported"); + } + return type.cast(getInstance(type, dataInput, ctx)); + } + + /** + * Returns whether this serializer can handle the given class type (perform + * reading and writing). + * + * @param type the type to be read or written + * @return true if this implementation can read/ write instances of this + * type, false otherwise. + */ + boolean accept(Class type); + +} diff --git a/src/main/java/io/github/proto4j/objection/Objection.java b/src/main/java/io/github/proto4j/objection/Objection.java new file mode 100644 index 0000000..47aa322 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/Objection.java @@ -0,0 +1,142 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection; //@date 26.08.2022 + +import io.github.proto4j.objection.internal.DefaultSharedConfiguration; + +import java.io.*; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Core functionalities of the {@code proto4j-objection} module are presented + * in this class. Methods can be used to set up the serialization and de- + * serialization process. + *

+ * Version history with any crucial changes to the system is presented below: + *

    + *
  • 0.2.0: initial beta release
  • + *
+ * + * @author MatrixEditor + * @version 0.2.0 + */ +public final class Objection { + + private Objection() {} + + /** + * Creates a new generic {@link Marshaller} instance with the default + * configuration values. + * + * @param the type this marshaller should be serializing and de-serializing + * @return a new generic {@link Marshaller} instance with the default + * configuration values. + */ + public static Marshaller createMarshaller() { + return createMarshaller(getDefaultConfiguration()); + } + + /** + * Creates a new generic {@link Marshaller} instance with the given + * configuration values. + * + * @param config the given configuration instance + * @param the type this marshaller should be serializing and de-serializing + * @return a new generic {@link Marshaller} instance with the given + * configuration values. + */ + public static Marshaller createMarshaller(OSharedConfiguration config) { + return new BasicMarshaller<>(config); + } + + /** + * Creates a new {@link DataInput} instance from the given {@link InputStream} + * object. The supplier can be used within {@link java.net.Socket} instances, + * for example: + *
+     *     Socket socket = new Socket(...);
+     *     DataInput input = Objection.createDataInput(socket::getInputStream());
+     * 
+ * + * @param supplier a {@link Supplier} of the resource stream + * @return a new {@link DataInput} instance from the given {@link InputStream} + * object + */ + public static DataInput createDataInput(Supplier supplier) { + return createDataInput(supplier.get()); + } + + /** + * Creates a new {@link DataInput} instance from the given {@link InputStream} + * object. + * + * @param inputStream the resource stream + * @return a new {@link DataInput} instance from the given {@link InputStream} + * object + */ + public static DataInput createDataInput(InputStream inputStream) { + Objects.requireNonNull(inputStream); + return new DataInputStream(inputStream); + } + + /** + * Creates a new {@link DataOutput} instance from the given {@link OutputStream} + * object. The supplier can be used within {@link java.net.Socket} instances, + * for example: + *
+     *     Socket socket = new Socket(...);
+     *     DataOutput output = Objection.createDataOutput(socket::getOutputStream());
+     * 
+ * + * @param supplier a {@link Supplier} of the resource output + * @return a new {@link DataOutput} instance from the given {@link OutputStream} + * object + */ + public static DataOutput createDataOutput(Supplier supplier) { + return createDataOutput(supplier.get()); + } + + /** + * Creates a new {@link DataOutput} instance from the given {@link OutputStream} + * object. + * + * @param outputStream the resource output + * @return a new {@link DataOutput} instance from the given {@link OutputStream} + * object + */ + public static DataOutput createDataOutput(OutputStream outputStream) { + Objects.requireNonNull(outputStream); + return new DataOutputStream(outputStream); + } + + /** + * @return a new instance for the default serialization configuration. + */ + public static OSharedConfiguration getDefaultConfiguration() { + return new DefaultSharedConfiguration(); + } + +} diff --git a/src/main/java/io/github/proto4j/objection/annotation/Serialize.java b/src/main/java/io/github/proto4j/objection/annotation/Serialize.java new file mode 100644 index 0000000..059f3df --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/annotation/Serialize.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.annotation;//@date 26.08.2022 + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Utility annotation used to indicate a class can be serialized with this + * API. + * + * @author MatrixEditor + * @version 0.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Serialize { + +} diff --git a/src/main/java/io/github/proto4j/objection/annotation/Transient.java b/src/main/java/io/github/proto4j/objection/annotation/Transient.java new file mode 100644 index 0000000..0b1111f --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/annotation/Transient.java @@ -0,0 +1,48 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.annotation;//@date 26.08.2022 + +import java.lang.annotation.*; + +/** + * This {@link Annotation} has the same effect for an annotated field as the + * keyword {@code transient}: + *
+ *     public class Example {
+ *         private transient int i;
+ *         // the same as
+ *         @Transient
+ *         private int j;
+ *     }
+ * 
+ * + * @author MatrixEditor + * @version 0.2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Transient { + +} diff --git a/src/main/java/io/github/proto4j/objection/annotation/Version.java b/src/main/java/io/github/proto4j/objection/annotation/Version.java new file mode 100644 index 0000000..95e7166 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/annotation/Version.java @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.annotation;//@date 26.08.2022 + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used for creating versioned serialized objects. This annotation can be really + * powerful for a versioned system that is backwards compatible: + *
+ *      @Version(1) // set class version to 1
+ *     class Example {
+ *           @Version(2) private int id2; // this field will be used in version 2
+ *           private int id; // no version means initial version = 0;
+ *     }
+ * 
+ * All annotated fields with a version number higher than the given value at + * the class definition will not be serialized. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) +public @interface Version { + /** + * Every class is annotated by default with this version number. + */ + int INITIAL_VERSION = 0; + + /** + * @return the configured version number + */ + byte value() default 0; +} diff --git a/src/main/java/io/github/proto4j/objection/internal/DefaultSharedConfiguration.java b/src/main/java/io/github/proto4j/objection/internal/DefaultSharedConfiguration.java new file mode 100644 index 0000000..cbdb515 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/internal/DefaultSharedConfiguration.java @@ -0,0 +1,71 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.internal; //@date 26.08.2022 + +import io.github.proto4j.objection.BasicSharedConfiguration; +import io.github.proto4j.objection.ObjectSerializer; +import io.github.proto4j.objection.serial.*; + +import java.util.Objects; + +public class DefaultSharedConfiguration extends BasicSharedConfiguration { + + public DefaultSharedConfiguration() { + getSerializers().add(new NumberSerializer.ByteSerializer()); + getSerializers().add(new NumberSerializer.CharacterSerializer()); + getSerializers().add(new NumberSerializer.DoubleSerializer()); + getSerializers().add(new NumberSerializer.FloatSerializer()); + getSerializers().add(new NumberSerializer.LongSerializer()); + getSerializers().add(new NumberSerializer.ShortSerializer()); + getSerializers().add(new NumberSerializer.IntegerSerializer()); + getSerializers().add(new OClassSerializer()); + getSerializers().add(new OFieldSerializer()); + getSerializers().add(new StringSerializer()); + getSerializers().add(new SequenceSerializer.CollectionSerializer()); + getSerializers().add(new SequenceSerializer.KeyValueSerializer()); + } + + @Override + public ObjectSerializer forType(Class type) { + ObjectSerializer sr = super.forType(type); + if (sr == null && type.isArray()) { + if (!type.getComponentType().isArray()) { + return SequenceSerializer.ArraySerializer.createArraySerializer(type.getComponentType()); + } else throw new UnsupportedOperationException("MultiArrays not implemented"); + } + return sr; + } + + public synchronized void addSerializer(ObjectSerializer serializer) { + Objects.requireNonNull(serializer); + getSerializers().add(serializer); + } + + public synchronized void addType(Class type) { + Objects.requireNonNull(type); + getRegisteredClasses().put(type.getName(), type); + } + +} diff --git a/src/main/java/io/github/proto4j/objection/internal/OReflection.java b/src/main/java/io/github/proto4j/objection/internal/OReflection.java new file mode 100644 index 0000000..e9f3730 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/internal/OReflection.java @@ -0,0 +1,49 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.internal; //@date 26.08.2022 + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Objects; +import java.util.Optional; + +public final class OReflection { + + private OReflection() {} + + public static Optional getAnnotation(AnnotatedElement element, Class cls) + throws NullPointerException { + Objects.requireNonNull(element); + Objects.requireNonNull(cls); + + A value = element.getAnnotation(cls); + return Optional.ofNullable(value); + } + + public static boolean isPresent(AnnotatedElement element, Class cls) + throws NullPointerException { + return getAnnotation(element, cls).isPresent(); + } +} diff --git a/src/main/java/io/github/proto4j/objection/model/OClass.java b/src/main/java/io/github/proto4j/objection/model/OClass.java new file mode 100644 index 0000000..2e58ebd --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/model/OClass.java @@ -0,0 +1,432 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.model;//@date 25.08.2022 + +import io.github.proto4j.objection.OSharedConfiguration; +import io.github.proto4j.objection.annotation.Serialize; +import io.github.proto4j.objection.annotation.Transient; +import io.github.proto4j.objection.annotation.Version; +import io.github.proto4j.objection.internal.OReflection; + +import java.io.Serializable; +import java.lang.ref.SoftReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.*; + +/** + *

OClass Type Wrapper

+ * A wrapper class designed to make information gathering on types that should + * be serialized or de-serialized much easier. Additionally, this API uses the + * {@link #newInstance()} for creating new instances and {@link #getInstance()} + * for getting already linked instances. + *

+ * The basic structure of binary serialized {@link OClass} objects is the + * following: + *

+ * +------------------------------------------------+
+ * | OClass of type T                               |
+ * +---------------+----------------+---------------+
+ * | version: byte | name_len: byte | name: byte[]  |
+ * +---------------++---------+-----+---------------+
+ * | modifiers: int | id: int | field_count: int    |
+ * +----------------+---------+---------------------+
+ * | fields: OField[]                               |
+ * | +--------------------------------------------+ |
+ * | | Field1:                                    | |
+ * | +------------+---------------+---------------+ |
+ * | | type: byte | version: byte | namelen: byte | |
+ * | +------------+-+-------------+---------------+ |
+ * | | name: byte[] | value: byte[]               | |
+ * | +--------------+-----------------------------+ |
+ * | ...                                            |
+ * +------------------------------------------------+
+ * 
+ * + * @param the class type stored in this class + * @author MatrixEditor + * @version 0.2.0 + */ +public final class OClass implements Type { + + /** + * The actual type linked to this instance. + */ + private final Class type; + + /** + * The class modifiers used for checksum identification. + */ + private final int modifiers; + + /** + * The class ID is computed with the hashcode of the linked type. + */ + private final int classId; + + /** + * Additional configuration link here to make this configuration accessible + * in the serialization process in earlier versions. + */ + private final OSharedConfiguration configuration; + + private transient volatile SoftReference> classInfoRef; + private transient volatile SoftReference instance; + + /** + * Create an OClass from the given type and configuration. Note the + * configuration is not used anymore in this class. This constructor is + * called via static factory methods and used in the de-serialization + * process. + * + * @param linkedClass the linked type + * @param configuration the serialization configuration + */ + private OClass(Class linkedClass, OSharedConfiguration configuration) { + this(linkedClass, configuration, null); + } + + /** + * Create an OClass from the given type, configuration and instance. Note the + * configuration is not used anymore in this class. This constructor is + * called via static factory methods and used in the serialization + * process. + * + * @param linkedClass the linked type + * @param configuration the serialization configuration + * @param value an instance of the given type + */ + private OClass(Class linkedClass, OSharedConfiguration configuration, T value) { + checkType(linkedClass); + this.type = Objects.requireNonNull(linkedClass); + this.modifiers = linkedClass.getModifiers(); + this.classId = linkedClass.hashCode(); + this.configuration = configuration; + this.instance = value != null ? new SoftReference<>(value) : null; + Objects.requireNonNull(classInfo()); + } + + /** + * Creates a new {@link OClass} instance for the given type. + * + * @param linkedClass the linked class type + * @param the linked type + * @return a new {@link OClass} instance for the given type. + */ + public static OClass klass(Class linkedClass) { + return klass(linkedClass, null); + } + + /** + * Creates a new {@link OClass} instance for the given type with an instance + * of the {@link OSharedConfiguration} class. + * + * @param linkedClass the linked class type + * @param configuration deprecated: leave this parameter null + * @param the linked type + * @return a new {@link OClass} instance for the given type. + */ + public static OClass klass(Class linkedClass, OSharedConfiguration configuration) { + return new OClass<>(linkedClass, configuration); + } + + /** + * Creates a new {@link OClass} instance for the given type instance. + * + * @param value the linked class type instance + * @param configuration deprecated: leave this parameter null + * @param the linked type + * @return a new {@link OClass} instance for the given type. + */ + public static OClass klass(T value, OSharedConfiguration configuration) { + //noinspection unchecked + return new OClass<>((Class) value.getClass(), configuration, value); + } + + /** + * @return the linked class type. + */ + public Class getType() { + return type; + } + + /** + * @return the linked class name (full name) + */ + public String getName() { + OClassInfo info = classInfo(); + return info.name; + } + + /** + * @return the linked class name as byte array + */ + public byte[] getBufferedName() { + OClassInfo info = classInfo(); + int length = info.bufferedName.length; + if (length == 0) { + return new byte[0]; + } + return Arrays.copyOf(info.bufferedName, length); + } + + /** + * @return if the linked class is annotated with + */ + public byte getVersion() { + OClassInfo info = classInfo(); + return info.version == -1 ? Version.INITIAL_VERSION : info.version; + } + + /** + * @return the hashcode of the linked class + */ + public int getClassId() { + return classId; + } + + /** + * @return the same as {@link #getType()}.getModifiers() + */ + public int getModifiers() { + return modifiers; + } + + /** + * @return an array of all serializable or de-serializable fields. + */ + public OField[] getDeclaredFields() { + OClassInfo info = classInfo(); + if (info.declaredFields.length == 0) { + return new OField[0]; + } + return Arrays.copyOf(info.declaredFields, info.declaredFields.length); + } + + /** + * @return the given configuration instance. + */ + public OSharedConfiguration getConfiguration() { + return this.configuration; + } + + /** + * @return an array of all usable constructors. + */ + public Constructor[] getDeclaredConstructors() { + OClassInfo info = classInfo(); + int length = info.declaredConstructors.length; + if (length == 0) { + return new Constructor[0]; + } + return Arrays.copyOf(info.declaredConstructors, length); + } + + /** + * @return the default T.$init() constructor. + */ + public Constructor getDefaultConstructor() { + OClassInfo info = classInfo(); + for (Constructor con : info.declaredConstructors) { + if (con.getParameterCount() == 0) { + // We already know that there are only Constructor types + // stored in the declaredConstructors array. + //noinspection unchecked + return (Constructor) con; + } + } + throw new NullPointerException("There is no default constructor for " + info.name); + } + + /** + * Returns the {@link OField} instance named with the given {@link String}. + * + * @param s the field's name + * @return the internal {@link OField} instance or null if no field with the + * given name is stored. + */ + public OField getDeclaredField(String s) { + for (OField field : getDeclaredFields()) { + if (field.getName().equals(s)) { + return field; + } + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getTypeName() { + return getType().getTypeName(); + } + + /** + * @return creates a new instance with the loaded values from a binary + * stream. + */ + public T newInstance() { + if (instance == null) { + createInstance(); + } + return instance.get(); + } + + /** + * @return the linked instance converted to a raw {@link Object}. + */ + public Object getInstance() { + return instance != null ? instance.get() : null; + } + + private OClassInfo classInfo() { + SoftReference> classInfo = this.classInfoRef; + OClassInfo info; + if (classInfo != null && (info = classInfo.get()) != null) { + return info; + } + return newClassInfo(); + } + + private OClassInfo newClassInfo() { + OClassInfo info = new OClassInfo<>(); + this.classInfoRef = new SoftReference<>(info); + if (type == null) { + return info; + } + + info.name = type.getName(); + info.bufferedName = info.name.getBytes(); + info.declaredConstructors = type.getConstructors(); + + Optional version = OReflection.getAnnotation(getType(), Version.class); + version.ifPresent(v -> info.version = v.value()); + + createFields(getType()); + return info; + } + + private void createFields(Class type) { + if (type.isEnum() || type.isInterface()) { + return; + } + + List fields = getAnnotatedFields(type.getDeclaredFields()); + while (type != Object.class) { + type = type.getSuperclass(); + if (type.isInterface()) break; + fields.addAll(getAnnotatedFields(type.getDeclaredFields())); + } + OClassInfo info = classInfo(); + + info.declaredFields = new OField[fields.size()]; + for (int i = 0; i < fields.size(); i++) { + Field field = fields.get(i); + info.declaredFields[i] = createField0(field); + } + } + + private List getAnnotatedFields(Field[] declaredFields) { + if (declaredFields.length == 0) { + return Collections.emptyList(); + } + List fields = new LinkedList<>(); + // Direct access is needed here because this method is called within + // the newClassInfo() method. The returned value is never null, because + // this method is called after all fields were set. + OClassInfo info = classInfoRef.get(); + + for (Field field : declaredFields) { + if (field.isSynthetic() || field.isEnumConstant()) { + continue; + } + + int mods = field.getModifiers(); + if (Modifier.isStatic(mods) || Modifier.isTransient(mods)) { + continue; + } + + if (OReflection.isPresent(field, Transient.class)) { + continue; + } + + Optional version = OReflection.getAnnotation(field, Version.class); + if (version.isPresent() && info != null) { + if (version.get().value() > info.version) { + continue; + } + } + + fields.add(field); + } + return fields; + } + + private OField createField0(Field field) { + return new OField(this, field); + } + + private void createInstance() { + T value = null; + try { + value = getDefaultConstructor().newInstance(); + for (OField field : getDeclaredFields()) { + Field f0 = field.getLinkedField(); + f0.setAccessible(true); + f0.set(value, field.getValue()); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } finally { + instance = new SoftReference<>(value); + } + } + + private void checkType(Class type) { + if (Serializable.class.isAssignableFrom(type) + || OReflection.isPresent(type, Serialize.class)) { + return; + } + throw new IllegalArgumentException("Class is not serializable"); + } + + private static class OClassInfo { + volatile OField[] declaredFields; + + // REVISIT: Changed declaredConstructors from typed version + // to a more generic version. The generic type of this class + // is remained for future usage. + volatile Constructor[] declaredConstructors; + + String name; + byte[] bufferedName; + byte version = Version.INITIAL_VERSION; + + OClassInfo() {} + } +} diff --git a/src/main/java/io/github/proto4j/objection/model/OField.java b/src/main/java/io/github/proto4j/objection/model/OField.java new file mode 100644 index 0000000..d777abe --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/model/OField.java @@ -0,0 +1,228 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.model;//@date 25.08.2022 + +import io.github.proto4j.objection.annotation.Version; +import io.github.proto4j.objection.internal.OReflection; + +import java.lang.ref.SoftReference; +import java.lang.reflect.Field; +import java.util.Objects; +import java.util.Optional; + +/** + * Instances of the {@code OField} represents single field objects stored in + * a specific {@link OClass}. It provides information about, and dynamic access + * to, a single field of an {@link OClass}. + *

+ * The binary structure of an OField instance can be summarized to the + * following: + *

+ *  +--------------------------------------------+
+ *  | Field1:                                    |
+ *  +------------+---------------+---------------+
+ *  | type: byte | version: byte | namelen: byte |
+ *  +------------+-+-------------+---------------+
+ *  | name: byte[] | value: byte[]               |
+ *  +--------------+-----------------------------+
+ * 
+ * + * @author MatrixEditor + * @version 0.2.0 + * @see OClass + */ +public final class OField { + + /** + * The {@code OField's} java reflect instance. + */ + private final Field reference; + + /** + * The Class instance describing what type this field is stored in. + */ + private final OClass parent; + + private final transient SoftReference fieldInfoRef; + + /** + * Creates a new OField instance from the given base type and {@link Field} + * reference. + * + * @param type the Class instance describing what type this field is stored in. + * @param ref the {@code OField's} java reflect instance + */ + public OField(OClass type, Field ref) { + this.parent = Objects.requireNonNull(type); + this.reference = Objects.requireNonNull(ref); + this.fieldInfoRef = new SoftReference<>(fieldInfo()); + } + + /** + * @return the type specification of this field + */ + public byte getFieldType() { + OFieldInfo info = fieldInfo(); + if (info == null) { + return 0; + } + return info.fieldType; + } + + /** + * @return the correlating {@link OFieldType} instance. + */ + public OFieldType getOFieldType() { + return OFieldType.valueOf(getFieldType()); + } + + /** + * @return the applied field version + */ + public byte getVersion() { + OFieldInfo info = fieldInfo(); + if (info == null) { + return 0; + } + return info.version; + } + + /** + * @return the field's name. + */ + public String getName() { + OFieldInfo info = fieldInfo(); + if (info == null) { + return ""; + } + return info.name; + } + + /** + * This field is the field class instance of the mapped field object. + * + * @return the type of the linked field. + */ + public Field getLinkedField() { + return this.reference; + } + + /** + * Returns a Class object that identifies the declared type for the field. + * + * @return a Class object identifying the declared type of the field + */ + public Class getLinkedFieldType() { + return fieldInfo().type; + } + + /** + * Returns the value of the field represented by this {@link OField}. + * + * @return the value of the field + */ + public Object getValue() { + OFieldInfo info = fieldInfo(); + if (info.value instanceof OClass) { + return ((OClass) info.value).newInstance(); + } + return info.value; + } + + /** + * Sets the field represented by this Field object on the specified object + * argument to the specified new value. + * + * @param value the new value for the field + */ + public void setValue(Object value) { + OFieldInfo info = fieldInfo(); + info.value = value; + } + + /** + * @return an {@code Class} object where this {@link OField} is located in. + */ + public OClass getParent() { + return parent; + } + + private OFieldInfo fieldInfo() { + SoftReference fieldInfo = this.fieldInfoRef; + OFieldInfo info; + if (fieldInfo != null && (info = fieldInfo.get()) != null) { + return info; + } + return newFieldInfo(); + } + + private OFieldInfo newFieldInfo() { + OFieldInfo info = new OFieldInfo(); + if (getLinkedField() == null) { + return info; + } + + info.name = getLinkedField().getName(); + info.fieldType = OFieldType.wrap(getLinkedField()); + info.type = getLinkedField().getType(); + + Optional version = OReflection.getAnnotation(getLinkedField(), Version.class); + info.version = version.map(Version::value).orElse((byte) 0); + + getLinkedField().setAccessible(true); + if (getParent().getInstance() != null) { + try { + info.value = getLinkedField().get(getParent().getInstance()); + } catch (ReflectiveOperationException e) { + throw new IllegalArgumentException( + "Could not read value for OField(" + info.name + "); " + e); + } + } + + if (getParent().getConfiguration().forType(info.type) == null) { + if (getParent().getInstance() != null) { + info.value = OClass.klass(info.value, getParent().getConfiguration()); + } else { + info.value = OClass.klass(info.type, getParent().getConfiguration()); + } + info.type = OClass.class; + } + + + return info; + } + + private static class OFieldInfo { + volatile String name; + volatile byte fieldType; + volatile byte version; + Object value; + volatile Class type; + + public OFieldInfo() { + } + } + +} diff --git a/src/main/java/io/github/proto4j/objection/model/OFieldType.java b/src/main/java/io/github/proto4j/objection/model/OFieldType.java new file mode 100644 index 0000000..7dc2a66 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/model/OFieldType.java @@ -0,0 +1,42 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.model;//@date 25.08.2022 + +import java.lang.reflect.Field; + +//! INFO: This type can be modified and should be used for internal checks. Yet, +//! it is not used in any context. +public enum OFieldType { + DEFAULT; + + public static OFieldType valueOf(byte type) { + // not implemented yet + return DEFAULT; + } + + public static byte wrap(Field linkedField) { + return 0; + } +} diff --git a/src/main/java/io/github/proto4j/objection/package-info.java b/src/main/java/io/github/proto4j/objection/package-info.java new file mode 100644 index 0000000..aad087c --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/package-info.java @@ -0,0 +1,72 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + *

Module Description:

+ * This module of the {@code proto4j} project is written to provide a fast + * and reliable object-serialization library. The structure of a serialized + * object and its contents is described below. + *

+ * Important: There is an option to check if the serialized class + * contains unsafe operations which is highly recommended to use. + * + *

Object Serialization Structure

+ * Every Object that should be serialized is wrapped into an + * {@link io.github.proto4j.objection.model.OClass} instance. It contains all relevant + * information for the serialization process. + *

+ * In general, primitive types directly written to the stream as described in + * the {@link io.github.proto4j.objection.serial.NumberSerializer} class info. Yet, it + * is not possible to read multidimensional arrays, but Collections, Maps and + * simple arrays.The basic binary structure is the following: + *

+ * +------------------------------------------------+
+ * | OClass of type T                               |
+ * +---------------+----------------+---------------+
+ * | version: byte | name_len: byte | name: byte[]  |
+ * +---------------++---------+-----+---------------+
+ * | modifiers: int | id: int | field_count: int    |
+ * +----------------+---------+---------------------+
+ * | fields: OField[]                               |
+ * | +--------------------------------------------+ |
+ * | | Field1:                                    | |
+ * | +------------+---------------+---------------+ |
+ * | | type: byte | version: byte | namelen: byte | |
+ * | +------------+-+-------------+---------------+ |
+ * | | name: byte[] | value: byte[]               | |
+ * | +--------------+-----------------------------+ |
+ * | ...                                            |
+ * +------------------------------------------------+
+ * 
+ * For a more detailed review of each individual value structure please refer + * to the related {@link io.github.proto4j.objection.ObjectSerializer} implementation. + * + * @author MatrixEditor + * @version 0.2.0 + * @see io.github.proto4j.objection.Objection + * @see io.github.proto4j.objection.ObjectSerializer + * @see io.github.proto4j.objection.Marshaller + * @see io.github.proto4j.objection.model.OClass + **/ +package io.github.proto4j.objection; \ No newline at end of file diff --git a/src/main/java/io/github/proto4j/objection/serial/NumberSerializer.java b/src/main/java/io/github/proto4j/objection/serial/NumberSerializer.java new file mode 100644 index 0000000..d905797 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/serial/NumberSerializer.java @@ -0,0 +1,175 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.serial; //@date 25.08.2022 + +import io.github.proto4j.objection.BasicObjectSerializer; +import io.github.proto4j.objection.OSerializationContext; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * A utility class containing all primitive serializers. They use the in-built + * methods by the {@link DataInput} and {@link DataOutput} objects. + * + * @author MatrixEditor + * @version 0.2.0 + */ +public class NumberSerializer { + + public static class LongSerializer extends BasicObjectSerializer { + + @Override + public boolean accept(Class type) { + return type == Long.class || type == Long.TYPE; + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) + throws IOException { + dataOutput.writeLong((long) writableObject); + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + return dataInput.readLong(); + } + } + + public static class IntegerSerializer extends BasicObjectSerializer { + + @Override + public boolean accept(Class type) { + return type == Integer.class || type == Integer.TYPE; + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) + throws IOException { + dataOutput.writeInt((int) writableObject); + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + return dataInput.readInt(); + } + } + + public static class ShortSerializer extends BasicObjectSerializer { + + @Override + public boolean accept(Class type) { + return type == Short.class || type == Short.TYPE; + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) + throws IOException { + dataOutput.writeShort((short) writableObject); + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + return dataInput.readShort(); + } + } + + public static class ByteSerializer extends BasicObjectSerializer { + + @Override + public boolean accept(Class type) { + return type == Byte.class || type == Byte.TYPE; + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) + throws IOException { + dataOutput.writeByte((byte) writableObject); + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + return dataInput.readByte(); + } + } + + public static class CharacterSerializer extends BasicObjectSerializer { + + @Override + public boolean accept(Class type) { + return type == Character.class || type == Character.TYPE; + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) + throws IOException { + dataOutput.writeChar((char) writableObject); + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + return dataInput.readChar(); + } + } + + public static class DoubleSerializer extends BasicObjectSerializer { + + @Override + public boolean accept(Class type) { + return type == Double.class || type == Double.TYPE; + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) + throws IOException { + dataOutput.writeDouble((double) writableObject); + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + return dataInput.readDouble(); + } + } + + public static class FloatSerializer extends BasicObjectSerializer { + + @Override + public boolean accept(Class type) { + return type == Float.class || type == Float.TYPE; + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) + throws IOException { + dataOutput.writeFloat((float) writableObject); + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + return dataInput.readFloat(); + } + } +} diff --git a/src/main/java/io/github/proto4j/objection/serial/OClassSerializer.java b/src/main/java/io/github/proto4j/objection/serial/OClassSerializer.java new file mode 100644 index 0000000..0cb7234 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/serial/OClassSerializer.java @@ -0,0 +1,124 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.serial; //@date 26.08.2022 + +import io.github.proto4j.objection.BasicObjectSerializer; +import io.github.proto4j.objection.BasicSerializationContext; +import io.github.proto4j.objection.OSerializationContext; +import io.github.proto4j.objection.ObjectSerializer; +import io.github.proto4j.objection.model.OClass; +import io.github.proto4j.objection.model.OField; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidClassException; + +/** + * A small wrapper for reading and writing {@link OClass} objects into a binary + * format. + * + * @author MatrixEditor + * @version 0.2.0 + * @see OClass + */ +public class OClassSerializer extends BasicObjectSerializer { + + /** + * {@inheritDoc} + */ + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) throws IOException { + OClass classInfo = (OClass) writableObject; + byte[] name = classInfo.getBufferedName(); + dataOutput.writeByte(classInfo.getVersion()); + dataOutput.writeByte(name.length); + dataOutput.write(name); + dataOutput.writeInt(classInfo.getModifiers()); + dataOutput.writeInt(classInfo.getClassId()); + + ObjectSerializer fsr = ctx.getConfig().forType(OField.class); + if (fsr == null) { + throw new NullPointerException("Could not serialize OField.class"); + } + OField[] fields = classInfo.getDeclaredFields(); + dataOutput.writeInt(fields.length); + + for (OField field : fields) { + fsr.writeObject(dataOutput, field, ctx); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + byte version = dataInput.readByte(); + byte nameLength = dataInput.readByte(); + byte[] name = new byte[nameLength]; + for (int i = 0; i < nameLength; i++) { + name[i] = dataInput.readByte(); + } + + Class linkedType; + try { + if (ctx.getConfig().isRegistered(name)) { + linkedType = ctx.getConfig().forName(name); + } else throw new InvalidClassException("Unsafe Operation: Class not defined!"); + + } catch (ClassNotFoundException e) { + throw new InvalidClassException(e.getMessage()); + + } finally { + for (int i = 0; i < nameLength; i++) { + name[i] = 0; + } + } + int mod = dataInput.readInt(); + int id = dataInput.readInt(); + OClass oClass = OClass.klass(linkedType, ctx.getConfig()); + if (oClass.getModifiers() != mod || oClass.getClassId() != id) { + throw new InvalidClassException("Invalid loaded class: Checksum mismatch"); + } + + OSerializationContext classCtx = new BasicSerializationContext(oClass, null, ctx.getConfig()); + ObjectSerializer serializer = ctx.getConfig().forType(OField.class); + + int field_count = dataInput.readInt(); + for (int i = 0; i < field_count; i++) { + serializer.getInstance(OField.class, dataInput, classCtx); + } + return oClass; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean accept(Class type) { + return type == OClass.class; + } +} diff --git a/src/main/java/io/github/proto4j/objection/serial/OFieldSerializer.java b/src/main/java/io/github/proto4j/objection/serial/OFieldSerializer.java new file mode 100644 index 0000000..7b67fb8 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/serial/OFieldSerializer.java @@ -0,0 +1,114 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.serial; //@date 25.08.2022 + +import io.github.proto4j.objection.BasicObjectSerializer; +import io.github.proto4j.objection.OSerializationContext; +import io.github.proto4j.objection.ObjectSerializer; +import io.github.proto4j.objection.model.OField; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.nio.charset.StandardCharsets; + +/** + * A small wrapper for reading and writing {@link OField} objects into a binary + * format. + * + * @author MatrixEditor + * @version 0.2.0 + * @see OField + */ +public class OFieldSerializer extends BasicObjectSerializer { + + /** + * {@inheritDoc} + */ + @Override + public boolean accept(Class type) { + return type == OField.class; + } + + /** + * {@inheritDoc} + */ + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) throws IOException { + OField reference = (OField) writableObject; + dataOutput.writeByte(reference.getFieldType()); + dataOutput.writeByte(reference.getVersion()); + + String name = reference.getName(); + dataOutput.writeByte((byte) name.length()); + dataOutput.writeBytes(name); + + Object value = reference.getValue(); + ObjectSerializer sr = ctx.getConfig().forType(value.getClass()); + if (sr != null) { + sr.writeObject(dataOutput, value, ctx); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + // reference is null + byte ftype = dataInput.readByte(); + byte version = dataInput.readByte(); + + byte name_len = dataInput.readByte(); + byte[] name = new byte[name_len]; + + for (int i = 0; i < name_len; i++) { + name[i] = dataInput.readByte(); + } + + OField field; + try { + field = ctx.getClassInfo().getDeclaredField(new String(name, StandardCharsets.UTF_8)); + if (field == null) { + throw new NullPointerException("Field not declared"); + } + if (version != field.getVersion() || ftype != field.getFieldType()) { + throw new InvalidObjectException("Field version of type does not match"); + } + } finally { + for (int i = 0; i < name_len; i++) { + name[i] = 0; + } + } + + ObjectSerializer sr = ctx.getConfig().forType(field.getLinkedFieldType()); + if (sr != null) { + field.setValue(sr.getInstance(field.getLinkedFieldType(), dataInput, ctx)); + } + return field; + } + +} diff --git a/src/main/java/io/github/proto4j/objection/serial/SequenceSerializer.java b/src/main/java/io/github/proto4j/objection/serial/SequenceSerializer.java new file mode 100644 index 0000000..dc21c57 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/serial/SequenceSerializer.java @@ -0,0 +1,416 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.serial; //@date 26.08.2022 + +import io.github.proto4j.objection.BasicObjectSerializer; +import io.github.proto4j.objection.OSerializationContext; +import io.github.proto4j.objection.ObjectSerializer; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidClassException; +import java.lang.reflect.Array; +import java.lang.reflect.Modifier; +import java.util.*; + +/** + * A utility class containing all sequence serializers. The types that can be + * serialized are: Arrays (one dimension), {@link Map}s and {@link Collection}s + * + * @author MatrixEditor + * @version 0.2.0 + */ +public class SequenceSerializer { + + /** + *
+     * ┌────────────────────────────────────────────────────────┐
+     * │ Array                                                  │
+     * ├──────────────────┬───────────────┬─────────────────────┤
+     * │ dimensions: byte │ dim0_len: int │ dim0_values: byte[] │
+     * └──────────────────┴───────────────┴─────────────────────┘
+     * 
+ */ + public static class ArraySerializer extends BasicObjectSerializer { + + private final Class componentType; + + private ArraySerializer(Class componentType) { + this.componentType = componentType; + } + + public static ArraySerializer createArraySerializer(Class componentType) { + return new ArraySerializer(componentType); + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + int dimensions = dataInput.readByte(); + if (dimensions > 1) { + throw new UnsupportedOperationException("Not implemented!"); + } + + ObjectSerializer sr = ctx.getConfig().forType(componentType); + + Object values = Array.newInstance(componentType, dataInput.readInt()); + int length = Array.getLength(values); + for (int i = 0; i < length; i++) { + Array.set(values, i, sr.getInstance(componentType, dataInput, ctx)); + } + + return values; + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) throws IOException { + ObjectSerializer sr = ctx.getConfig().forType(componentType); + int length = Array.getLength(writableObject); + dataOutput.writeInt(length); + + for (int i = 0; i < length; i++) { + sr.writeObject(dataOutput, Array.get(writableObject, i), ctx); + } + } + + @Override + public boolean accept(Class type) { + return componentType.isAssignableFrom(type); + } + } + + /** + *
+     * ┌──────────────────────────────────────────┐
+     * │ List                                     │
+     * ├────────────────┬──────────────┬──────────┤
+     * │ type_len: byte │ type: byte[] │ len: int │
+     * ├────────────────┴──────────────┴──────────┤
+     * │ v1 v2 ...                                │
+     * └──────────────────────────────────────────┘
+     * 
+ */ + public static class CollectionSerializer extends BasicObjectSerializer { + + @Override + public boolean accept(Class type) { + return Collection.class.isAssignableFrom(type); + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) throws IOException { + Collection collection = (Collection) writableObject; + Object[] values = collection.toArray(); + + if (values.length != 0) { + Class c = values[0].getClass(); + + byte[] name = c.getName().getBytes(); + dataOutput.writeByte(name.length); + dataOutput.write(name); + dataOutput.writeInt(values.length); + + ObjectSerializer sr = ctx.getConfig().forType(c); + for (Object o : values) { + sr.writeObject(dataOutput, o, ctx); + } + } else { + dataOutput.writeByte(0); + } + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + byte len = dataInput.readByte(); + byte[] name = new byte[len]; + Collection collection; + Class componentType = null; + + if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { + if (List.class.isAssignableFrom(type) || Collection.class.isAssignableFrom(type)) { + type = ArrayList.class; + } else if (Set.class.isAssignableFrom(type)) { + type = HashSet.class; + } else if (Queue.class.isAssignableFrom(type)) { + type = ArrayDeque.class; + } + } + + try { + for (int i = 0; i < len; i++) { + name[i] = dataInput.readByte(); + } + + if (len > 0) { + componentType = Class.forName(new String(name)); + } + //noinspection unchecked + collection = (Collection) type.getDeclaredConstructor().newInstance(); + } catch (ReflectiveOperationException e) { + throw new InvalidClassException(e.getMessage()); + + } finally { + for (int i = 0; i < len; i++) { + name[i] = 0; + } + } + if (len > 0) { + int length = dataInput.readInt(); + ObjectSerializer sr = ctx.getConfig().forType(componentType); + for (int i = 0; i < length; i++) { + collection.add(sr.getInstance(componentType, dataInput, ctx)); + } + } + return collection; + } + } + + /** + *
+     * ┌────────────────────────────────────────────────────┐
+     * │ Map                                                │
+     * ├───────────────┬──────────────────┬─────────────────┤
+     * │ key_len: byte │ key_type: byte[] │ value_len: byte │
+     * ├───────────────┴────┬─────────────┼─────────────────┤
+     * │ value_type: byte[] │ amount: int │ k1 v1 k2 v2 ... │
+     * └────────────────────┴─────────────┴─────────────────┘
+     * 
+ */ + public static class KeyValueSerializer extends BasicObjectSerializer { + + @Override + public boolean accept(Class type) { + return Map.class.isAssignableFrom(type); + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) throws IOException { + Map map = (Map) writableObject; + int size = map.size(); + Class keyType; + Class valueType; + byte[] name = new byte[0]; + int nameLength = 0; + + if (size == 0) { + dataOutput.writeByte(0); + dataOutput.writeByte(0); + dataOutput.writeInt(0); + return; + } + try { + Object[] keys = map.keySet().toArray(); + Object[] values = map.values().toArray(); + + keyType = keys[0].getClass(); + valueType = values[0].getClass(); + + name = keyType.getName().getBytes(); + nameLength = name.length; + dataOutput.writeByte(nameLength); + dataOutput.write(name); + + name = valueType.getName().getBytes(); + nameLength = name.length; + dataOutput.writeByte(nameLength); + dataOutput.write(name); + + dataOutput.writeInt(size); + ObjectSerializer srK = ctx.getConfig().forType(keyType); + ObjectSerializer srV = ctx.getConfig().forType(valueType); + if (srK == null || srV == null) { + throw new IllegalArgumentException("Key or Value type can not be serialized!"); + } + + for (int i = 0; i < size; i++) { + srK.writeObject(dataOutput, keys[i], ctx); + srV.writeObject(dataOutput, values[i], ctx); + } + } finally { + for (int i = 0; i < nameLength; i++) { + name[i] = 0; + } + } + } + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + Map map; + int size; + Class keyType; + Class valueType; + ObjectSerializer srK, srV; + + try { + keyType = getType(dataInput, ctx); + valueType = getType(dataInput, ctx); + + if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { + type = HashMap.class; + } + + //noinspection unchecked + map = (Map) type.getDeclaredConstructor().newInstance(); + size = dataInput.readInt(); + if (size == 0) { + return map; + } + srK = ctx.getConfig().forType(keyType); + srV = ctx.getConfig().forType(valueType); + + if (srK == null || srV == null) { + throw new IllegalArgumentException("Key or Value type can not be serialized!"); + } + for (int i = 0; i < size; i++) { + map.put(srK.getInstance(keyType, dataInput, ctx), srV.getInstance(valueType, dataInput, ctx)); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + return map; + } + + private Class getType(DataInput dataInput, OSerializationContext ctx) + throws IOException, ClassNotFoundException { + int nameLength = dataInput.readByte(); + if (nameLength == 0) return null; + + byte[] name = new byte[nameLength]; + for (int i = 0; i < nameLength; i++) { + name[i] = dataInput.readByte(); + } + + return Class.forName(new String(name)); + } + } + + + @Deprecated + public static class MultiDimensionArraySerializer extends BasicObjectSerializer { + + private final Class baseType; + private final Class componentType; + + private final int dimensions; + + public MultiDimensionArraySerializer(Class baseType) { + this.baseType = baseType; + + Class type = baseType; + int amount = 1; + while ((type = type.getComponentType()).isArray()) { + amount++; + } + this.componentType = type; + this.dimensions = amount; + } + + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) throws IOException { + int[] size = new int[dimensions]; + + Object last = writableObject; + for (int i = 0; i < dimensions; i++) { + if (last == null) { + size[i] = 0; + continue; + } + size[i] = Array.getLength(last); + if (size[i] == 0) { + last = null; + } else last = Array.get(last, 0); + } + + dataOutput.writeByte(dimensions); + for (int j : size) { + dataOutput.writeInt(j); + } + + ObjectSerializer sr = ctx.getConfig().forType(componentType); + int len = Array.getLength(writableObject); + writeArray(dataOutput, writableObject, len, sr, ctx); + } + + private void writeArray(DataOutput dataOutput, Object array, int length, ObjectSerializer sr, + OSerializationContext ctx) throws IOException { + for (int i = 0; i < length; i++) { + Object value = Array.get(array, i); + if (value.getClass().isArray()) { + writeArray(dataOutput, value, Array.getLength(value), sr, ctx); + } else { + sr.writeObject(dataOutput, value, ctx); + } + } + } + + + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + int sDimensions = dataInput.readByte(); + if (sDimensions != dimensions) { + throw new IllegalArgumentException("Serialized dimensions mismatch"); + } + + int[] size = new int[dimensions]; + for (int i = 0; i < size.length; i++) { + size[i] = dataInput.readInt(); + } + + Object array = Array.newInstance(componentType, size); + readArray(array, dataInput, size, ctx, ctx.getConfig().forType(componentType)); + return array; + + } + + private void readArray(Object array, DataInput dataInput, int[] size, + OSerializationContext ctx, ObjectSerializer sr) throws IOException { + Object base; + if (Array.getLength(array) == 0) return; + while ((base = Array.get(array, 0)).getClass().isArray()) { + if (base.getClass().getComponentType().isArray() + && base.getClass().getComponentType().getComponentType() == componentType) { + break; + } + } + + for (int i = 0; i < size[size.length - 2]; i++) { + Object next = Array.get(base, i); + for (int j = 0; j < Array.getLength(next); j++) { + Array.set(next, j, sr.getInstance(componentType, dataInput, ctx)); + } + } + } + + public Class getBaseType() { + return baseType; + } + + @Override + public boolean accept(Class type) { + return type.getName().startsWith("[["); + } + } +} + diff --git a/src/main/java/io/github/proto4j/objection/serial/StringSerializer.java b/src/main/java/io/github/proto4j/objection/serial/StringSerializer.java new file mode 100644 index 0000000..3c99a69 --- /dev/null +++ b/src/main/java/io/github/proto4j/objection/serial/StringSerializer.java @@ -0,0 +1,72 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.proto4j.objection.serial; //@date 26.08.2022 + +import io.github.proto4j.objection.BasicObjectSerializer; +import io.github.proto4j.objection.OSerializationContext; +import io.github.proto4j.objection.serial.SequenceSerializer.ArraySerializer; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Alternative version of the {@link ArraySerializer} for componentType {@link Byte}. + */ +public class StringSerializer extends BasicObjectSerializer { + + /** + * {@inheritDoc} + */ + @Override + public void writeObject(DataOutput dataOutput, Object writableObject, OSerializationContext ctx) throws IOException { + String value = writableObject.toString(); + dataOutput.writeInt(value.length()); + dataOutput.writeBytes(value); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getInstance(Class type, DataInput dataInput, OSerializationContext ctx) throws IOException { + int len = dataInput.readInt(); + byte[] string = new byte[len]; + for (int i = 0; i < len; i++) { + string[i] = dataInput.readByte(); + } + + return new String(string, StandardCharsets.UTF_8); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean accept(Class type) { + return type == String.class; + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..29593c8 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,40 @@ +/* + * MIT License + * + * Copyright (c) 2022 Proto4j + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import io.github.proto4j.objection.BasicMarshaller; +import io.github.proto4j.objection.Marshaller; + +/** + * This module declaration ensures all components except these in package + * {@code org.proto4j.objection.internal} are exported. + */ +module proto4j.objection { + + exports io.github.proto4j.objection; + exports io.github.proto4j.objection.annotation; + exports io.github.proto4j.objection.model; + exports io.github.proto4j.objection.serial; + + provides Marshaller with BasicMarshaller; +} \ No newline at end of file diff --git a/src/main/test/io/github/proto4j/test/objection/ArraysTest.java b/src/main/test/io/github/proto4j/test/objection/ArraysTest.java new file mode 100644 index 0000000..d2c7355 --- /dev/null +++ b/src/main/test/io/github/proto4j/test/objection/ArraysTest.java @@ -0,0 +1,28 @@ +package io.github.proto4j.test.objection; //@date 27.08.2022 + +import io.github.proto4j.objection.Marshaller; +import io.github.proto4j.objection.OSharedConfiguration; +import io.github.proto4j.objection.Objection; + +import java.awt.*; +import java.io.*; + +public class ArraysTest { + + public static void main(String[] args) throws ReflectiveOperationException, IOException { + Marshaller marshaller = Objection.createMarshaller(); + ByteArrayOutputStream file = new ByteArrayOutputStream(); + + Point a0 = new Point(1, 2); + + // 1. Serialize data + DataOutput output = new DataOutputStream(file); + OSharedConfiguration config = marshaller.marshall(a0, output); + + // 2. De-Serialize data + DataInput input = new DataInputStream(new ByteArrayInputStream(file.toByteArray())); + Point a1 = marshaller.getInstance(input, config); + + assert a0.equals(a1); // see ArrayModel::equals + } +} diff --git a/src/main/test/io/github/proto4j/test/objection/ListsTest.java b/src/main/test/io/github/proto4j/test/objection/ListsTest.java new file mode 100644 index 0000000..bec3b5f --- /dev/null +++ b/src/main/test/io/github/proto4j/test/objection/ListsTest.java @@ -0,0 +1,31 @@ +package io.github.proto4j.test.objection; //@date 28.08.2022 + +import io.github.proto4j.objection.Marshaller; +import io.github.proto4j.objection.OSharedConfiguration; +import io.github.proto4j.objection.Objection; +import io.github.proto4j.test.objection.model.Lists; + +import java.io.*; +import java.util.LinkedList; +import java.util.List; + +public class ListsTest { + + public static void main(String[] args) throws ReflectiveOperationException, IOException { + Marshaller marshaller = Objection.createMarshaller(); + ByteArrayOutputStream file = new ByteArrayOutputStream(); + + Lists a0 = new Lists(List.of("Hello", "World"), new LinkedList<>()); + + // 1. Serialize data + DataOutput output = new DataOutputStream(file); + OSharedConfiguration config = marshaller.marshall(a0, output); + + // 2. De-Serialize data + DataInput input = new DataInputStream(new ByteArrayInputStream(file.toByteArray())); + Lists a1 = marshaller.getInstance(input, config); + + assert a0.equals(a1) : "Not equal"; // see ArrayModel::equals + } + +} diff --git a/src/main/test/io/github/proto4j/test/objection/MapsTest.java b/src/main/test/io/github/proto4j/test/objection/MapsTest.java new file mode 100644 index 0000000..c504a2e --- /dev/null +++ b/src/main/test/io/github/proto4j/test/objection/MapsTest.java @@ -0,0 +1,34 @@ +package io.github.proto4j.test.objection; //@date 28.08.2022 + +import io.github.proto4j.objection.Marshaller; +import io.github.proto4j.objection.OSharedConfiguration; +import io.github.proto4j.objection.Objection; +import io.github.proto4j.test.objection.model.Maps; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +public class MapsTest { + + public static void main(String[] args) throws ReflectiveOperationException, IOException { + Marshaller marshaller = Objection.createMarshaller(); + ByteArrayOutputStream file = new ByteArrayOutputStream(); + + Map map = new HashMap<>(); + map.put("Hello", 10L); + map.put("World", -11L); + Maps a0 = new Maps(map); + + // 1. Serialize data + DataOutput output = new DataOutputStream(file); + OSharedConfiguration config = marshaller.marshall(a0, output); + + // 2. De-Serialize data + DataInput input = new DataInputStream(new ByteArrayInputStream(file.toByteArray())); + Maps a1 = marshaller.getInstance(input, config); + + assert a0.equals(a1) : "Not equal"; // see ArrayModel::equals + } + +} diff --git a/src/main/test/io/github/proto4j/test/objection/MultiArrayTest.java b/src/main/test/io/github/proto4j/test/objection/MultiArrayTest.java new file mode 100644 index 0000000..92aa165 --- /dev/null +++ b/src/main/test/io/github/proto4j/test/objection/MultiArrayTest.java @@ -0,0 +1,14 @@ +package io.github.proto4j.test.objection; //@date 28.08.2022 + +import io.github.proto4j.objection.ObjectSerializer; +import io.github.proto4j.objection.serial.SequenceSerializer; + +public class MultiArrayTest { + + public static void main(String[] args) { + int[][][] ints = new int[4][5][3]; + + ObjectSerializer serializer = new SequenceSerializer.MultiDimensionArraySerializer(ints.getClass()); + } + +} diff --git a/src/main/test/io/github/proto4j/test/objection/PrimitiveTest.java b/src/main/test/io/github/proto4j/test/objection/PrimitiveTest.java new file mode 100644 index 0000000..54d8b9f --- /dev/null +++ b/src/main/test/io/github/proto4j/test/objection/PrimitiveTest.java @@ -0,0 +1,31 @@ +package io.github.proto4j.test.objection; //@date 27.08.2022 + + +import io.github.proto4j.objection.Marshaller; +import io.github.proto4j.objection.OSharedConfiguration; +import io.github.proto4j.objection.Objection; +import io.github.proto4j.test.objection.model.Primitives; + +import java.io.*; + +//! Model class: .model.Primitives +public class PrimitiveTest { + + public static void main(String[] args) throws ReflectiveOperationException, IOException { + Marshaller marshaller = Objection.createMarshaller(); + ByteArrayOutputStream file = new ByteArrayOutputStream(); + + // Test values beginning by 1: + Primitives p0 = new Primitives(1, 2.2f, 3.3, (char) 4, (byte) 5, (short) 6, 7); + + // 1. Serialize data + DataOutput output = new DataOutputStream(file); + OSharedConfiguration config = marshaller.marshall(p0, output); + + // 2. De-Serialize data + DataInput input = new DataInputStream(new ByteArrayInputStream(file.toByteArray())); + Primitives p1 = marshaller.getInstance(input, config); + + assert p0.equals(p1); + } +} diff --git a/src/main/test/io/github/proto4j/test/objection/model/ArrayModel.java b/src/main/test/io/github/proto4j/test/objection/model/ArrayModel.java new file mode 100644 index 0000000..4aafd06 --- /dev/null +++ b/src/main/test/io/github/proto4j/test/objection/model/ArrayModel.java @@ -0,0 +1,36 @@ +package io.github.proto4j.test.objection.model; //@date 27.08.2022 + +import io.github.proto4j.objection.annotation.Serialize; +import io.github.proto4j.objection.annotation.Transient; + +import java.util.Arrays; + +@Serialize +public class ArrayModel { + + // Using the Objection-Annotation @Transient to prevent this field from + // being serialized. + @Transient + private Object[] values; + + private final int[] i; + private final String[] s; + + public ArrayModel(int[] i, String[] s) { + this.i = i; + this.s = s; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ArrayModel that = (ArrayModel) o; + + if (!Arrays.equals(i, that.i)) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + return Arrays.equals(s, that.s, String::compareTo); + } + +} diff --git a/src/main/test/io/github/proto4j/test/objection/model/Lists.java b/src/main/test/io/github/proto4j/test/objection/model/Lists.java new file mode 100644 index 0000000..49e6728 --- /dev/null +++ b/src/main/test/io/github/proto4j/test/objection/model/Lists.java @@ -0,0 +1,32 @@ +package io.github.proto4j.test.objection.model; //@date 28.08.2022 + +import java.io.Serializable; +import java.util.LinkedList; +import java.util.List; + +public class Lists implements Serializable { + + // this list will be de-serialized as an ArrayList + private List strings; + + private List integers; + + public Lists() { + } + + public Lists(List strings, LinkedList integers) { + this.strings = strings; + this.integers = integers; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Lists lists = (Lists) o; + + return strings.containsAll(lists.strings) && integers.containsAll(lists.integers); + } + +} diff --git a/src/main/test/io/github/proto4j/test/objection/model/Maps.java b/src/main/test/io/github/proto4j/test/objection/model/Maps.java new file mode 100644 index 0000000..ade08f4 --- /dev/null +++ b/src/main/test/io/github/proto4j/test/objection/model/Maps.java @@ -0,0 +1,30 @@ +package io.github.proto4j.test.objection.model; //@date 28.08.2022 + +import io.github.proto4j.objection.annotation.Serialize; + +import java.util.Map; +import java.util.Objects; + +@Serialize +public class Maps { + + private Map map; + + public Maps() { + } + + public Maps(Map map) { + this.map = map; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Maps maps = (Maps) o; + + return Objects.equals(map, maps.map); + } + +} diff --git a/src/main/test/io/github/proto4j/test/objection/model/Primitives.java b/src/main/test/io/github/proto4j/test/objection/model/Primitives.java new file mode 100644 index 0000000..9f674b8 --- /dev/null +++ b/src/main/test/io/github/proto4j/test/objection/model/Primitives.java @@ -0,0 +1,50 @@ +package io.github.proto4j.test.objection.model; //@date 27.08.2022 + +import io.github.proto4j.objection.annotation.Serialize; + +@Serialize +public class Primitives { + + private int i; + + // standard Java serialization keyword to prevent this variable from + // being serialized. + private transient long notUsed; + + private float f; + private double d; + private char c; + private byte b; + private short s; + private long l; + + public Primitives() { + } + + public Primitives(int i, float f, double d, char c, byte b, short s, long l) { + this.i = i; + this.f = f; + this.d = d; + this.c = c; + this.b = b; + this.s = s; + this.l = l; + } + + @Override // generated + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Primitives that = (Primitives) o; + + if (i != that.i) return false; + if (Float.compare(that.f, f) != 0) return false; + if (Double.compare(that.d, d) != 0) return false; + if (c != that.c) return false; + if (b != that.b) return false; + if (s != that.s) return false; + return l == that.l; + } + +}