You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sd...@apache.org on 2021/12/22 09:16:05 UTC
[ignite-3] branch main updated: IGNITE-16154 Implement (un)marshalling of Externalizable
This is an automated email from the ASF dual-hosted git repository.
sdanilov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new e953d0c IGNITE-16154 Implement (un)marshalling of Externalizable
e953d0c is described below
commit e953d0c9ab4f9d1a4802300557c27855f2a97218
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Sun Dec 19 16:52:57 2021 +0400
IGNITE-16154 Implement (un)marshalling of Externalizable
---
.../{DefaultType.java => BuiltinType.java} | 14 +-
.../network/serialization/ClassDescriptor.java | 58 ++++++-
.../serialization/ClassDescriptorFactory.java | 108 ++++--------
.../ClassDescriptorFactoryContext.java | 16 +-
.../network/serialization/Serialization.java | 97 +++++++++++
.../network/serialization/SerializationType.java | 32 ++--
.../marshal/DefaultUserObjectMarshaller.java | 181 +++++++++++++++++++++
.../serialization/marshal/MarshalException.java | 29 ++++
.../serialization/marshal/MarshalledObject.java | 67 ++++++++
.../serialization/marshal/ReflectionException.java | 30 ++++
.../marshal/SpecialSerializationMethods.java | 151 +++++++++++++++++
.../marshal/SpecialSerializationMethodsCache.java | 33 ++++
.../serialization/marshal/UnmarshalException.java | 29 ++++
.../marshal/UserObjectMarshaller.java | 44 +++++
.../serialization/ClassDescriptorFactoryTest.java | 91 +++++------
.../serialization/DefaultDescriptorsTest.java | 90 +++++-----
.../marshal/DefaultUserObjectMarshallerTest.java | 159 ++++++++++++++++++
17 files changed, 1031 insertions(+), 198 deletions(-)
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/DefaultType.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java
similarity index 93%
rename from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/DefaultType.java
rename to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java
index 8b10b11..2094d83 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/DefaultType.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java
@@ -30,9 +30,9 @@ import java.util.LinkedList;
import org.apache.ignite.lang.IgniteUuid;
/**
- * Default types.
+ * Built-in types.
*/
-public enum DefaultType {
+public enum BuiltinType {
BYTE(0, byte.class),
BYTE_BOXED(1, Byte.class),
SHORT(2, short.class),
@@ -95,7 +95,7 @@ public enum DefaultType {
* @param descriptorId Descriptor id.
* @param clazz Type.
*/
- DefaultType(int descriptorId, Class<?> clazz) {
+ BuiltinType(int descriptorId, Class<?> clazz) {
this.descriptorId = descriptorId;
this.clazz = clazz;
}
@@ -125,10 +125,10 @@ public enum DefaultType {
*/
public ClassDescriptor asClassDescriptor() {
return new ClassDescriptor(
- clazz,
- descriptorId,
- Collections.emptyList(),
- SerializationType.DEFAULT
+ clazz,
+ descriptorId,
+ Collections.emptyList(),
+ new Serialization(SerializationType.BUILTIN)
);
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
index 78f2873..0b80c82 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
@@ -49,9 +49,9 @@ public class ClassDescriptor {
private final List<FieldDescriptor> fields;
/**
- * The type of the serialization mechanism for the class.
+ * How the class is to be serialized.
*/
- private final int serializationType;
+ private final Serialization serialization;
/**
* Whether the class is final.
@@ -61,12 +61,12 @@ public class ClassDescriptor {
/**
* Constructor.
*/
- public ClassDescriptor(@NotNull Class<?> clazz, int descriptorId, @NotNull List<FieldDescriptor> fields, int serializationType) {
+ public ClassDescriptor(@NotNull Class<?> clazz, int descriptorId, @NotNull List<FieldDescriptor> fields, Serialization serialization) {
this.className = clazz.getName();
this.clazz = clazz;
this.descriptorId = descriptorId;
this.fields = List.copyOf(fields);
- this.serializationType = serializationType;
+ this.serialization = serialization;
this.isFinal = Modifier.isFinal(clazz.getModifiers());
}
@@ -110,12 +110,21 @@ public class ClassDescriptor {
}
/**
+ * Returns serialization.
+ *
+ * @return Serialization.
+ */
+ public Serialization serialization() {
+ return serialization;
+ }
+
+ /**
* Returns serialization type.
*
* @return Serialization type.
*/
- public int serializationType() {
- return serializationType;
+ public SerializationType serializationType() {
+ return serialization.type();
}
/**
@@ -126,4 +135,41 @@ public class ClassDescriptor {
public boolean isFinal() {
return isFinal;
}
+
+ /**
+ * Returns {@code true} if the described class should be serialized as a {@link java.io.Serializable} (but not
+ * using the mechanism for {@link java.io.Externalizable}).
+ *
+ * @return {@code true} if the described class should be serialized as a {@link java.io.Serializable}.
+ */
+ public boolean isSerializable() {
+ return serialization.type() == SerializationType.SERIALIZABLE;
+ }
+
+ /**
+ * Returns {@code true} if the described class should be serialized as an {@link java.io.Externalizable}.
+ *
+ * @return {@code true} if the described class should be serialized as an {@link java.io.Externalizable}.
+ */
+ public boolean isExternalizable() {
+ return serialization.type() == SerializationType.EXTERNALIZABLE;
+ }
+
+ /**
+ * Returns {@code true} if the described class has {@code writeReplace()} method.
+ *
+ * @return {@code true} if the described class has {@code writeReplace()} method
+ */
+ public boolean hasWriteReplace() {
+ return serialization.hasWriteReplace();
+ }
+
+ /**
+ * Returns {@code true} if the described class has {@code readResolve()} method.
+ *
+ * @return {@code true} if the described class has {@code readResolve()} method
+ */
+ public boolean hasReadResolve() {
+ return serialization.hasReadResolve();
+ }
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
index 9e345ae..31fe206 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactory.java
@@ -18,10 +18,8 @@
package org.apache.ignite.internal.network.serialization;
import java.io.Externalizable;
-import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
-import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
@@ -31,7 +29,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
-import java.util.Set;
import java.util.stream.Collectors;
import org.apache.ignite.lang.IgniteException;
import org.jetbrains.annotations.Nullable;
@@ -40,6 +37,9 @@ import org.jetbrains.annotations.Nullable;
* Class descriptor factory for the user object serialization.
*/
public class ClassDescriptorFactory {
+ /** Means that no serialization override is present; used for readability instead of {@code false}. */
+ private static final boolean NO_SERIALIZATION_OVERRIDE = false;
+
/**
* Factory context.
*/
@@ -122,10 +122,15 @@ public class ClassDescriptorFactory {
checkHasPublicNoArgConstructor(clazz);
return new ClassDescriptor(
- clazz,
- descriptorId,
- Collections.emptyList(),
- SerializationType.EXTERNALIZABLE
+ clazz,
+ descriptorId,
+ Collections.emptyList(),
+ new Serialization(
+ SerializationType.EXTERNALIZABLE,
+ NO_SERIALIZATION_OVERRIDE,
+ hasWriteReplace(clazz),
+ hasReadResolve(clazz)
+ )
);
}
@@ -161,30 +166,30 @@ public class ClassDescriptorFactory {
* @return Class descriptor.
*/
private ClassDescriptor serializable(int descriptorId, Class<? extends Serializable> clazz) {
- Method writeObject = getWriteObject(clazz);
- Method readObject = getReadObject(clazz);
- Method readObjectNoData = getReadObjectNoData(clazz);
-
- boolean overrideSerialization = writeObject != null && readObject != null && readObjectNoData != null;
-
- Method writeReplace = getWriteReplace(clazz);
- Method readResolve = getReadResolve(clazz);
-
- int serializationType = SerializationType.SERIALIZABLE;
+ return new ClassDescriptor(clazz, descriptorId, fields(clazz),
+ new Serialization(
+ SerializationType.SERIALIZABLE,
+ hasOverrideSerialization(clazz),
+ hasWriteReplace(clazz),
+ hasReadResolve(clazz)
+ )
+ );
+ }
- if (overrideSerialization) {
- serializationType |= SerializationType.SERIALIZABLE_OVERRIDE;
- }
+ private boolean hasReadResolve(Class<? extends Serializable> clazz) {
+ return getReadResolve(clazz) != null;
+ }
- if (writeReplace != null) {
- serializationType |= SerializationType.SERIALIZABLE_WRITE_REPLACE;
- }
+ private boolean hasWriteReplace(Class<? extends Serializable> clazz) {
+ return getWriteReplace(clazz) != null;
+ }
- if (readResolve != null) {
- serializationType |= SerializationType.SERIALIZABLE_READ_RESOLVE;
- }
+ private boolean hasOverrideSerialization(Class<? extends Serializable> clazz) {
+ Method writeObject = getWriteObject(clazz);
+ Method readObject = getReadObject(clazz);
+ Method readObjectNoData = getReadObjectNoData(clazz);
- return new ClassDescriptor(clazz, descriptorId, fields(clazz), serializationType);
+ return writeObject != null && readObject != null && readObjectNoData != null;
}
/**
@@ -195,7 +200,7 @@ public class ClassDescriptorFactory {
* @return Class descriptor.
*/
private ClassDescriptor arbitrary(int descriptorId, Class<?> clazz) {
- return new ClassDescriptor(clazz, descriptorId, fields(clazz), SerializationType.ARBITRARY);
+ return new ClassDescriptor(clazz, descriptorId, fields(clazz), new Serialization(SerializationType.ARBITRARY));
}
/**
@@ -231,13 +236,7 @@ public class ClassDescriptorFactory {
@Nullable
private static Method getWriteReplace(Class<? extends Serializable> clazz) {
try {
- Method writeReplace = clazz.getDeclaredMethod("writeReplace");
-
- if (!declaresExactExceptions(writeReplace, Set.of(ObjectStreamException.class))) {
- return null;
- }
-
- return writeReplace;
+ return clazz.getDeclaredMethod("writeReplace");
} catch (NoSuchMethodException e) {
return null;
}
@@ -253,13 +252,7 @@ public class ClassDescriptorFactory {
@Nullable
private static Method getReadResolve(Class<? extends Serializable> clazz) {
try {
- Method readResolve = clazz.getDeclaredMethod("readResolve");
-
- if (!declaresExactExceptions(readResolve, Set.of(ObjectStreamException.class))) {
- return null;
- }
-
- return readResolve;
+ return clazz.getDeclaredMethod("readResolve");
} catch (NoSuchMethodException e) {
return null;
}
@@ -281,10 +274,6 @@ public class ClassDescriptorFactory {
return null;
}
- if (!declaresExactExceptions(writeObject, Set.of(IOException.class))) {
- return null;
- }
-
return writeObject;
} catch (NoSuchMethodException e) {
return null;
@@ -308,10 +297,6 @@ public class ClassDescriptorFactory {
return null;
}
- if (!declaresExactExceptions(writeObject, Set.of(IOException.class, ClassNotFoundException.class))) {
- return null;
- }
-
return writeObject;
} catch (NoSuchMethodException e) {
return null;
@@ -334,32 +319,9 @@ public class ClassDescriptorFactory {
return null;
}
- if (!declaresExactExceptions(writeObject, Set.of(ObjectStreamException.class))) {
- return null;
- }
-
return writeObject;
} catch (NoSuchMethodException e) {
return null;
}
}
-
- /**
- * Returns {@code true} if the method's declaration contains throwing only of
- * specified exceptions.
- *
- * @param method Method.
- * @param exceptions Set of exceptions.
- * @return If the method throws exceptions.
- */
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- private static boolean declaresExactExceptions(Method method, Set<Class<? extends Throwable>> exceptions) {
- Class<?>[] exceptionTypes = method.getExceptionTypes();
-
- if (exceptionTypes.length != exceptions.size()) {
- return false;
- }
-
- return Arrays.asList(exceptionTypes).containsAll(exceptions);
- }
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryContext.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryContext.java
index 92ff68a..43b148c 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryContext.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryContext.java
@@ -42,7 +42,7 @@ public class ClassDescriptorFactoryContext {
* Constructor.
*/
public ClassDescriptorFactoryContext() {
- for (DefaultType value : DefaultType.values()) {
+ for (BuiltinType value : BuiltinType.values()) {
addPredefinedDescriptor(value.clazz(), value.asClassDescriptor());
}
}
@@ -104,6 +104,20 @@ public class ClassDescriptorFactoryContext {
}
/**
+ * Returns a descriptor by its ID or throws an exception if the ID is not known.
+ *
+ * @param descriptorId ID by which to obtain a descriptor
+ * @return descriptor
+ */
+ public ClassDescriptor getRequiredDescriptor(int descriptorId) {
+ ClassDescriptor descriptor = getDescriptor(descriptorId);
+ if (descriptor == null) {
+ throw new IllegalStateException("No descriptor exists with ID=" + descriptorId);
+ }
+ return descriptor;
+ }
+
+ /**
* Returns {@code true} if there is a descriptor for the id.
*
* @param descriptorId Descriptor id.
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/Serialization.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/Serialization.java
new file mode 100644
index 0000000..0f58c70
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/Serialization.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization;
+
+/**
+ * Describes how a class is to be serialized.
+ */
+public class Serialization {
+ /** Serialization type. */
+ private final SerializationType type;
+
+ /** Whether a Serializable has writeObject()/readObject()/readObjectNoData() methods. */
+ private final boolean hasSerializationOverride;
+ /** Whether a Serializable/Externalizable has writeReplace() method. */
+ private final boolean hasWriteReplace;
+ /** Whether a Serializable/Externalizable has readResolve() method. */
+ private final boolean hasReadResolve;
+
+ /**
+ * Creates a new Serialization.
+ *
+ * @param type type
+ * @param hasSerializationOverride whether a Serializable has writeObject()/readObject()/readObjectNoData() methods
+ * @param hasWriteReplace whether a Serializable/Externalizable has writeReplace() method
+ * @param hasReadResolve whether a Serializable/Externalizable has readResolve() method
+ */
+ public Serialization(SerializationType type, boolean hasSerializationOverride, boolean hasWriteReplace, boolean hasReadResolve) {
+ assert type == SerializationType.SERIALIZABLE
+ || (type == SerializationType.EXTERNALIZABLE && !hasSerializationOverride)
+ || (!hasSerializationOverride && !hasWriteReplace && !hasReadResolve);
+
+ this.type = type;
+ this.hasSerializationOverride = hasSerializationOverride;
+ this.hasWriteReplace = hasWriteReplace;
+ this.hasReadResolve = hasReadResolve;
+ }
+
+ /**
+ * Creates a new Serialization with all optional features disabled.
+ *
+ * @param type serialization type
+ */
+ public Serialization(SerializationType type) {
+ this(type, false, false, false);
+ }
+
+ /**
+ * Returns serialization type.
+ *
+ * @return serialization type
+ */
+ public SerializationType type() {
+ return type;
+ }
+
+ /**
+ * Returns whether serialization override (writeObject()/readObject()/readObjectNoData()) is present.
+ *
+ * @return whether serialization override (writeObject()/readObject()/readObjectNoData()) is present
+ */
+ public boolean hasSerializationOverride() {
+ return hasSerializationOverride;
+ }
+
+ /**
+ * Returns whether writeReplace() method is present.
+ *
+ * @return whether writeReplace() method is present
+ */
+ public boolean hasWriteReplace() {
+ return hasWriteReplace;
+ }
+
+ /**
+ * Returns whether readResolve() method is present.
+ *
+ * @return whether readResolve() method is present
+ */
+ public boolean hasReadResolve() {
+ return hasReadResolve;
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationType.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationType.java
index 99b462f..c5cc87d 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationType.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationType.java
@@ -18,27 +18,25 @@
package org.apache.ignite.internal.network.serialization;
/**
- * Serialization type flags.
+ * Serialization type.
*/
-public class SerializationType {
- /** Used for predefined descriptors like primitive (or boxed int). See {@link DefaultType}. */
- public static final int DEFAULT = 0;
-
+public enum SerializationType {
+ /** Used for predefined descriptors like primitive (or boxed int). See {@link BuiltinType}. */
+ BUILTIN(0),
/** Type for classes that are neither serializable nor externalizable. */
- public static final int ARBITRARY = 1;
-
+ ARBITRARY(1),
/** Externalizable. */
- public static final int EXTERNALIZABLE = 1 << 1;
-
- /** Simple serializable. */
- public static final int SERIALIZABLE = 1 << 2;
+ EXTERNALIZABLE(2),
+ /** Serializable (but not Externalizable). */
+ SERIALIZABLE(3);
- /** Serializable with readObject/writeObject flag. */
- public static final int SERIALIZABLE_OVERRIDE = 1 << 3;
+ private final int value;
- /** Serializable with writeReplace flag. */
- public static final int SERIALIZABLE_WRITE_REPLACE = 1 << 4;
+ SerializationType(int value) {
+ this.value = value;
+ }
- /** Serializable with readResolve flag. */
- public static final int SERIALIZABLE_READ_RESOLVE = 1 << 5;
+ public int value() {
+ return value;
+ }
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
new file mode 100644
index 0000000..17b8644
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+
+/**
+ * Default implementation of {@link UserObjectMarshaller}.
+ */
+public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
+ private final ClassDescriptorFactoryContext descriptorRegistry;
+ private final ClassDescriptorFactory descriptorFactory;
+
+ private final SpecialSerializationMethodsCache serializationMethodsCache = new SpecialSerializationMethodsCache();
+
+ public DefaultUserObjectMarshaller(ClassDescriptorFactoryContext descriptorRegistry, ClassDescriptorFactory descriptorFactory) {
+ this.descriptorRegistry = descriptorRegistry;
+ this.descriptorFactory = descriptorFactory;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public MarshalledObject marshal(Object object) throws MarshalException {
+ ClassDescriptor descriptor = getOrCreateDescriptor(object);
+
+ var baos = new ByteArrayOutputStream();
+
+ final List<ClassDescriptor> usedDescriptors;
+ try (var dos = new DataOutputStream(baos)) {
+ writeDescriptorId(descriptor, dos);
+
+ usedDescriptors = writeObject(object, descriptor, dos);
+ } catch (IOException e) {
+ throw new MarshalException("Cannot marshal", e);
+ }
+
+ return new MarshalledObject(baos.toByteArray(), usedDescriptors);
+ }
+
+ private ClassDescriptor getOrCreateDescriptor(Object object) {
+ ClassDescriptor descriptor = descriptorRegistry.getDescriptor(object.getClass());
+ if (descriptor == null) {
+ descriptor = descriptorFactory.create(object.getClass());
+ }
+ return descriptor;
+ }
+
+ private void writeDescriptorId(ClassDescriptor descriptor, DataOutputStream dos) throws IOException {
+ dos.writeInt(descriptor.descriptorId());
+ }
+
+ private List<ClassDescriptor> writeObject(Object object, ClassDescriptor descriptor, DataOutputStream dos)
+ throws IOException, MarshalException {
+ final Object objectToWrite;
+ if ((descriptor.isSerializable() || descriptor.isExternalizable()) && descriptor.hasWriteReplace()) {
+ // TODO: IGNITE-16155 what if non-Externalizable is returned?
+ objectToWrite = applyWriteReplace(object, descriptor);
+ } else {
+ objectToWrite = object;
+ }
+
+ if (descriptor.isExternalizable()) {
+ return writeExternalizable((Externalizable) objectToWrite, descriptor, dos);
+ } else {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+ }
+
+ private Object applyWriteReplace(Object object, ClassDescriptor descriptor) throws MarshalException {
+ return serializationMethodsCache.methodsFor(descriptor).writeReplace(object);
+
+ // TODO: IGNITE-16155 what if null is returned?
+ }
+
+ private List<ClassDescriptor> writeExternalizable(
+ Externalizable externalizable,
+ ClassDescriptor descriptor,
+ DataOutputStream dos
+ ) throws IOException {
+ byte[] externalizableBytes = externalize(externalizable);
+
+ dos.writeInt(externalizableBytes.length);
+ dos.write(externalizableBytes);
+
+ return List.of(descriptor);
+ }
+
+ private byte[] externalize(Externalizable externalizable) throws IOException {
+ var baos = new ByteArrayOutputStream();
+ try (var oos = new ObjectOutputStream(baos)) {
+ externalizable.writeExternal(oos);
+ }
+
+ return baos.toByteArray();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <T> T unmarshal(byte[] bytes) throws UnmarshalException {
+ try (var dis = new DataInputStream(new ByteArrayInputStream(bytes))) {
+ int descriptorId = readDescriptorId(dis);
+ ClassDescriptor descriptor = descriptorRegistry.getRequiredDescriptor(descriptorId);
+
+ if (descriptor.isExternalizable()) {
+ Object readObject = readExternalizable(descriptor, dis);
+ Object resultObject;
+ if (descriptor.hasReadResolve()) {
+ resultObject = applyReadResolve(readObject, descriptor);
+ } else {
+ resultObject = readObject;
+ }
+ @SuppressWarnings("unchecked") T castResult = (T) resultObject;
+ return castResult;
+ } else {
+ throw new UnsupportedOperationException("Not supported yet");
+ }
+ } catch (IOException | ClassNotFoundException e) {
+ throw new UnmarshalException("Cannot unmarshal", e);
+ }
+ }
+
+ private int readDescriptorId(DataInputStream dis) throws IOException {
+ return dis.readInt();
+ }
+
+ private <T extends Externalizable> T readExternalizable(ClassDescriptor descriptor, DataInputStream dis)
+ throws IOException, ClassNotFoundException, UnmarshalException {
+ T object = instantiateObject(descriptor);
+
+ int length = dis.readInt();
+ byte[] bytes = new byte[length];
+ dis.readFully(bytes);
+
+ try (var ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
+ object.readExternal(ois);
+ }
+
+ return object;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T extends Externalizable> T instantiateObject(ClassDescriptor descriptor) throws UnmarshalException {
+ try {
+ return (T) descriptor.clazz().getConstructor().newInstance();
+ } catch (ReflectiveOperationException e) {
+ throw new UnmarshalException("Cannot instantiate " + descriptor.clazz(), e);
+ }
+ }
+
+ private Object applyReadResolve(Object object, ClassDescriptor descriptor) throws UnmarshalException {
+ return serializationMethodsCache.methodsFor(descriptor).readResolve(object);
+
+ // TODO: IGNITE-16155 what if null is returned?
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshalException.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshalException.java
new file mode 100644
index 0000000..7406a92
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshalException.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import org.apache.ignite.lang.IgniteInternalCheckedException;
+
+/**
+ * Thrown when marshalling fails.
+ */
+public class MarshalException extends IgniteInternalCheckedException {
+ public MarshalException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshalledObject.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshalledObject.java
new file mode 100644
index 0000000..96bb887
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshalledObject.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+
+/**
+ * Represents a marshalled object: the marshalled representation with information about how it was marshalled
+ * (including what descriptors were used when marshalling it).
+ */
+public class MarshalledObject {
+ /** Marshalled object representation. */
+ private final byte[] bytes;
+
+ /** The descriptors that were used while marshalling the object. */
+ private final List<ClassDescriptor> usedDescriptors;
+
+ /**
+ * Creates a new {@link MarshalledObject}.
+ *
+ * @param bytes marshalled representation bytes
+ * @param usedDescriptors the descriptors that were used to marshal the object
+ */
+ public MarshalledObject(byte[] bytes, List<ClassDescriptor> usedDescriptors) {
+ Objects.requireNonNull(bytes, "bytes is null");
+ Objects.requireNonNull(usedDescriptors, "usedDescriptors is null");
+
+ this.bytes = Arrays.copyOf(bytes, bytes.length);
+ this.usedDescriptors = List.copyOf(usedDescriptors);
+ }
+
+ /**
+ * Returns marshalled object representation.
+ *
+ * @return marshalled object representation
+ */
+ public byte[] bytes() {
+ return Arrays.copyOf(bytes, bytes.length);
+ }
+
+ /**
+ * Returns the descriptors that were used while marshalling the object.
+ *
+ * @return the descriptors that were used while marshalling the object
+ */
+ public List<ClassDescriptor> usedDescriptors() {
+ return usedDescriptors;
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ReflectionException.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ReflectionException.java
new file mode 100644
index 0000000..91be33a
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ReflectionException.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import org.apache.ignite.lang.IgniteInternalException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Thrown when a reflection operation fails.
+ */
+public class ReflectionException extends IgniteInternalException {
+ public ReflectionException(String msg, @Nullable Throwable cause) {
+ super(msg, cause);
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SpecialSerializationMethods.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SpecialSerializationMethods.java
new file mode 100644
index 0000000..e8dce7c
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SpecialSerializationMethods.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.util.Objects;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Encapsulates special serialization methods like writeReplace()/readResolve() for convenient invocation.
+ */
+class SpecialSerializationMethods {
+ /** MethodHandle that can be used to invoke writeReplace() on the target class. */
+ @Nullable
+ private final MethodHandle writeReplaceHandle;
+
+ /** MethodHandle that can be used to invoke readResolve() on the target class. */
+ @Nullable
+ private final MethodHandle readResolveHandle;
+
+ /**
+ * Creates a new instance from the provided descriptor.
+ *
+ * @param descriptor class descriptor on which class to operate
+ */
+ public SpecialSerializationMethods(ClassDescriptor descriptor) {
+ writeReplaceHandle = descriptor.hasWriteReplace() ? writeReplaceHandle(descriptor) : null;
+ readResolveHandle = descriptor.hasReadResolve() ? readResolveHandle(descriptor) : null;
+ }
+
+ private static MethodHandle writeReplaceHandle(ClassDescriptor descriptor) {
+ Method writeReplaceMethod = findWriteReplaceMethod(descriptor);
+
+ try {
+ return MethodHandles.lookup()
+ .unreflect(writeReplaceMethod)
+ .asType(MethodType.methodType(Object.class, Object.class));
+ } catch (IllegalAccessException e) {
+ throw new ReflectionException("writeReplace() cannot be unreflected", e);
+ }
+ }
+
+ private static Method findWriteReplaceMethod(ClassDescriptor descriptor) {
+ Method writeReplaceMethod;
+ try {
+ writeReplaceMethod = descriptor.clazz().getDeclaredMethod("writeReplace");
+ } catch (NoSuchMethodException e) {
+ throw new ReflectionException("writeReplace() was not found on " + descriptor.clazz()
+ + " even though the descriptor says the class has the method", e);
+ }
+
+ writeReplaceMethod.setAccessible(true);
+
+ return writeReplaceMethod;
+ }
+
+ private static MethodHandle readResolveHandle(ClassDescriptor descriptor) {
+ Method readResolveMethod = findReadResolveMethod(descriptor);
+
+ try {
+ return MethodHandles.lookup()
+ .unreflect(readResolveMethod)
+ .asType(MethodType.methodType(Object.class, Object.class));
+ } catch (IllegalAccessException e) {
+ throw new ReflectionException("readResolve() cannot be unreflected", e);
+ }
+ }
+
+ @NotNull
+ private static Method findReadResolveMethod(ClassDescriptor descriptor) {
+ Method readResolveMethod;
+ try {
+ readResolveMethod = descriptor.clazz().getDeclaredMethod("readResolve");
+ } catch (NoSuchMethodException e) {
+ throw new ReflectionException("readResolve() was not found on " + descriptor.clazz()
+ + " even though the descriptor says the class has the method", e);
+ }
+
+ readResolveMethod.setAccessible(true);
+
+ return readResolveMethod;
+ }
+
+ /**
+ * Invokes {@code writeReplace()} on the target object. Should only be used if the target descriptor supports
+ * the method (that is, its serialization type is
+ * {@link org.apache.ignite.internal.network.serialization.SerializationType#SERIALIZABLE} or
+ * {@link org.apache.ignite.internal.network.serialization.SerializationType#EXTERNALIZABLE} and the class
+ * actually has writeReplace() method).
+ * If any of these conditions fail, a {@link NullPointerException} will be thrown.
+ *
+ * @param object target object on which to invoke the method
+ * @return invocation result
+ * @throws MarshalException if the invocation fails
+ */
+ public Object writeReplace(Object object) throws MarshalException {
+ Objects.requireNonNull(writeReplaceHandle);
+
+ try {
+ return writeReplaceHandle.invokeExact(object);
+ } catch (Error e) {
+ throw e;
+ } catch (Throwable e) {
+ throw new MarshalException("writeReplace() invocation failed on " + object, e);
+ }
+ }
+
+ /**
+ * Invokes {@code readResolve()} on the target object. Should only be used if the target descriptor supports
+ * the method (that is, its serialization type is
+ * {@link org.apache.ignite.internal.network.serialization.SerializationType#SERIALIZABLE} or
+ * {@link org.apache.ignite.internal.network.serialization.SerializationType#EXTERNALIZABLE} and the class
+ * actually has readResolve() method).
+ * If any of these conditions fail, a {@link NullPointerException} will be thrown.
+ *
+ * @param object target object on which to invoke the method
+ * @return invocation result
+ * @throws UnmarshalException if the invocation fails
+ */
+ public Object readResolve(Object object) throws UnmarshalException {
+ Objects.requireNonNull(readResolveHandle);
+
+ try {
+ return readResolveHandle.invokeExact(object);
+ } catch (Error e) {
+ throw e;
+ } catch (Throwable e) {
+ throw new UnmarshalException("readResolve() invocation failed on " + object, e);
+ }
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SpecialSerializationMethodsCache.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SpecialSerializationMethodsCache.java
new file mode 100644
index 0000000..1be3cf3
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SpecialSerializationMethodsCache.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+
+/**
+ * Caches {@link SpecialSerializationMethods} per class descriptor.
+ */
+class SpecialSerializationMethodsCache {
+ private final ConcurrentMap<Integer, SpecialSerializationMethods> methodsMap = new ConcurrentHashMap<>();
+
+ public SpecialSerializationMethods methodsFor(ClassDescriptor descriptor) {
+ return methodsMap.computeIfAbsent(descriptor.descriptorId(), id -> new SpecialSerializationMethods(descriptor));
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshalException.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshalException.java
new file mode 100644
index 0000000..9e7b624
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshalException.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import org.apache.ignite.lang.IgniteInternalCheckedException;
+
+/**
+ * Thrown when unmarshalling fails.
+ */
+public class UnmarshalException extends IgniteInternalCheckedException {
+ public UnmarshalException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UserObjectMarshaller.java
new file mode 100644
index 0000000..0b75743
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UserObjectMarshaller.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+/**
+ * Marshals/unmarshals objects in accordance with User Object Serialization.
+ *
+ * @see <a href="https://github.com/gridgain/gridgain-9-ce/blob/iep-67/modules/network/README.md">IEP-67</a>
+ */
+public interface UserObjectMarshaller {
+ /**
+ * Marshals the provided object.
+ *
+ * @param object object to marshal
+ * @return marshalled representation
+ * @throws MarshalException if marshalling fails
+ */
+ MarshalledObject marshal(Object object) throws MarshalException;
+
+ /**
+ * Unmarshals an object.
+ *
+ * @param bytes bytes representing the marshalled object
+ * @param <T> expected type
+ * @return unmarshalled object
+ * @throws UnmarshalException if unmarshalling fails
+ */
+ <T> T unmarshal(byte[] bytes) throws UnmarshalException;
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java
index 0c15e73..ca5ab1c 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorFactoryTest.java
@@ -20,10 +20,8 @@ package org.apache.ignite.internal.network.serialization;
import static org.apache.ignite.internal.network.serialization.SerializationType.ARBITRARY;
import static org.apache.ignite.internal.network.serialization.SerializationType.EXTERNALIZABLE;
import static org.apache.ignite.internal.network.serialization.SerializationType.SERIALIZABLE;
-import static org.apache.ignite.internal.network.serialization.SerializationType.SERIALIZABLE_OVERRIDE;
-import static org.apache.ignite.internal.network.serialization.SerializationType.SERIALIZABLE_READ_RESOLVE;
-import static org.apache.ignite.internal.network.serialization.SerializationType.SERIALIZABLE_WRITE_REPLACE;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -64,7 +62,7 @@ public class ClassDescriptorFactoryTest {
public void testSerializable() {
ClassDescriptor descriptor = factory.create(SerializableClass.class);
- checkSerializable(descriptor.serializationType(), false, false, false);
+ checkSerializable(descriptor.serialization(), false, false, false);
assertEquals(0, descriptor.fields().size());
}
@@ -89,7 +87,7 @@ public class ClassDescriptorFactoryTest {
public void testSerializableOverride() {
ClassDescriptor descriptor = factory.create(SerializableOverride.class);
- checkSerializable(descriptor.serializationType(), true, false, false);
+ checkSerializable(descriptor.serialization(), true, false, false);
assertEquals(0, descriptor.fields().size());
}
@@ -119,7 +117,7 @@ public class ClassDescriptorFactoryTest {
public void testSerializableOverrideWriteReplace() {
ClassDescriptor descriptor = factory.create(SerializableOverrideWriteReplace.class);
- checkSerializable(descriptor.serializationType(), true, true, false);
+ checkSerializable(descriptor.serialization(), true, true, false);
assertEquals(0, descriptor.fields().size());
}
@@ -149,7 +147,7 @@ public class ClassDescriptorFactoryTest {
public void testSerializableOverrideReadResolve() {
ClassDescriptor descriptor = factory.create(SerializableOverrideReadResolve.class);
- checkSerializable(descriptor.serializationType(), true, false, true);
+ checkSerializable(descriptor.serialization(), true, false, true);
assertEquals(0, descriptor.fields().size());
}
@@ -185,7 +183,7 @@ public class ClassDescriptorFactoryTest {
ClassDescriptor descriptor = factory.create(
SerializableOverrideWriteReplaceReadResolve.class);
- checkSerializable(descriptor.serializationType(), true, true, true);
+ checkSerializable(descriptor.serialization(), true, true, true);
assertEquals(0, descriptor.fields().size());
}
@@ -202,7 +200,7 @@ public class ClassDescriptorFactoryTest {
public void testSerializableWriteReplace() {
ClassDescriptor descriptor = factory.create(SerializableWriteReplace.class);
- checkSerializable(descriptor.serializationType(), false, true, false);
+ checkSerializable(descriptor.serialization(), false, true, false);
assertEquals(0, descriptor.fields().size());
}
@@ -220,7 +218,7 @@ public class ClassDescriptorFactoryTest {
public void testSerializableReadResolve() {
ClassDescriptor descriptor = factory.create(SerializableReadResolve.class);
- checkSerializable(descriptor.serializationType(), false, false, true);
+ checkSerializable(descriptor.serialization(), false, false, true);
assertEquals(0, descriptor.fields().size());
}
@@ -242,7 +240,7 @@ public class ClassDescriptorFactoryTest {
public void testSerializableWriteReplaceReadResolve() {
ClassDescriptor descriptor = factory.create(SerializableWriteReplaceReadResolve.class);
- checkSerializable(descriptor.serializationType(), false, true, true);
+ checkSerializable(descriptor.serialization(), false, true, true);
assertEquals(0, descriptor.fields().size());
}
@@ -266,7 +264,7 @@ public class ClassDescriptorFactoryTest {
public void testExternalizable() {
ClassDescriptor descriptor = factory.create(ExternalizableClass.class);
- checkExternalizable(descriptor.serializationType());
+ checkSimpleExternalizable(descriptor.serialization());
assertEquals(0, descriptor.fields().size());
}
@@ -277,7 +275,7 @@ public class ClassDescriptorFactoryTest {
public void testArbitrary() {
ClassDescriptor descriptor = factory.create(ArbitraryClass.class);
- checkArbitraryType(descriptor.serializationType());
+ checkArbitraryType(descriptor.serialization());
assertEquals(0, descriptor.fields().size());
}
@@ -328,7 +326,7 @@ public class ClassDescriptorFactoryTest {
ClassDescriptor arbitraryDescriptor = context.getDescriptor(ArbitraryClass.class);
assertNotNull(arbitraryDescriptor);
- ClassDescriptor intDescriptor = context.getDescriptor(DefaultType.INT.descriptorId());
+ ClassDescriptor intDescriptor = context.getDescriptor(BuiltinType.INT.descriptorId());
assertNotNull(intDescriptor);
List<FieldDescriptor> fields = holderDescriptor.fields();
@@ -358,10 +356,10 @@ public class ClassDescriptorFactoryTest {
ClassDescriptor descriptor = context.getDescriptor(int.class);
assertNotNull(descriptor);
- checkDefaultType(descriptor.serializationType());
+ checkBuiltInType(descriptor.serializationType());
}
- private static class SerializableWithWrongSignatures implements Serializable {
+ private static class SerializableWithoutExceptionsOnSpecialMethods implements Serializable {
private static final long serialVersionUID = 0L;
private void writeObject(ObjectOutputStream out) {
@@ -388,64 +386,59 @@ public class ClassDescriptorFactoryTest {
}
@Test
- public void testSerializableWithIncorrectMethodSignatures() {
- ClassDescriptor descriptor = factory.create(SerializableWithWrongSignatures.class);
+ public void testSerializableWithoutExceptionsOnSpecialMethods() {
+ ClassDescriptor descriptor = factory.create(SerializableWithoutExceptionsOnSpecialMethods.class);
assertNotNull(descriptor);
- checkSerializable(descriptor.serializationType(), false, false, false);
+ checkSerializable(descriptor.serialization(), true, true, true);
}
/**
* Checks that serialization type is {@link SerializationType#ARBITRARY}.
*
- * @param type Serialization type.
+ * @param serialization Serialization.
*/
- private void checkArbitraryType(int type) {
- assertEquals(ARBITRARY, type & ARBITRARY);
- assertEquals(0, type & SERIALIZABLE);
- assertEquals(0, type & SERIALIZABLE_OVERRIDE);
- assertEquals(0, type & SERIALIZABLE_WRITE_REPLACE);
- assertEquals(0, type & SERIALIZABLE_READ_RESOLVE);
- assertEquals(0, type & EXTERNALIZABLE);
+ private void checkArbitraryType(Serialization serialization) {
+ assertEquals(ARBITRARY, serialization.type());
+ assertFalse(serialization.hasSerializationOverride());
+ assertFalse(serialization.hasWriteReplace());
+ assertFalse(serialization.hasReadResolve());
}
/**
- * Checks that serialization type is {@link SerializationType#DEFAULT}.
+ * Checks that serialization type is {@link SerializationType#BUILTIN}.
*
* @param type Serialization type.
*/
- private void checkDefaultType(int type) {
- assertEquals(0, type);
+ private void checkBuiltInType(SerializationType type) {
+ assertEquals(SerializationType.BUILTIN, type);
}
/**
* Checks that serialization type is {@link SerializationType#EXTERNALIZABLE}.
*
- * @param type Serialization type.
+ * @param serialization Serialization type.
*/
- private void checkExternalizable(int type) {
- assertEquals(0, type & ARBITRARY);
- assertEquals(0, type & SERIALIZABLE);
- assertEquals(0, type & SERIALIZABLE_OVERRIDE);
- assertEquals(0, type & SERIALIZABLE_WRITE_REPLACE);
- assertEquals(0, type & SERIALIZABLE_READ_RESOLVE);
- assertEquals(EXTERNALIZABLE, type & EXTERNALIZABLE);
+ private void checkSimpleExternalizable(Serialization serialization) {
+ assertEquals(EXTERNALIZABLE, serialization.type());
+ assertFalse(serialization.hasSerializationOverride());
+ assertFalse(serialization.hasWriteReplace());
+ assertFalse(serialization.hasReadResolve());
}
/**
* Checks that serialization type is {@link SerializationType#SERIALIZABLE} and has specific flags.
*
- * @param type Serialization type.
- * @param override Has readObject/writeObject/readObjectNoData methods.
- * @param writeReplace Has writeReplace method.
- * @param readResolve Has readResolve method.
+ * @param serialization serialization to check.
+ * @param override Has readObject/writeObject/readObjectNoData methods.
+ * @param writeReplace Has writeReplace method.
+ * @param readResolve Has readResolve method.
*/
- private void checkSerializable(int type, boolean override, boolean writeReplace, boolean readResolve) {
- assertEquals(0, type & ARBITRARY);
- assertEquals(SERIALIZABLE, type & SERIALIZABLE);
- assertEquals(override ? SERIALIZABLE_OVERRIDE : 0, type & SERIALIZABLE_OVERRIDE);
- assertEquals(writeReplace ? SERIALIZABLE_WRITE_REPLACE : 0, type & SERIALIZABLE_WRITE_REPLACE);
- assertEquals(readResolve ? SERIALIZABLE_READ_RESOLVE : 0, type & SERIALIZABLE_READ_RESOLVE);
- assertEquals(0, type & EXTERNALIZABLE);
+ private void checkSerializable(Serialization serialization, boolean override, boolean writeReplace, boolean readResolve) {
+ assertEquals(SERIALIZABLE, serialization.type());
+
+ assertEquals(override, serialization.hasSerializationOverride());
+ assertEquals(writeReplace, serialization.hasWriteReplace());
+ assertEquals(readResolve, serialization.hasReadResolve());
}
}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java
index a9c5fb5..a663562 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java
@@ -17,51 +17,51 @@
package org.apache.ignite.internal.network.serialization;
-import static org.apache.ignite.internal.network.serialization.DefaultType.ARRAY_LIST;
-import static org.apache.ignite.internal.network.serialization.DefaultType.BIT_SET;
-import static org.apache.ignite.internal.network.serialization.DefaultType.BOOLEAN;
-import static org.apache.ignite.internal.network.serialization.DefaultType.BOOLEAN_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.BOOLEAN_BOXED;
-import static org.apache.ignite.internal.network.serialization.DefaultType.BYTE;
-import static org.apache.ignite.internal.network.serialization.DefaultType.BYTE_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.BYTE_BOXED;
-import static org.apache.ignite.internal.network.serialization.DefaultType.CHAR;
-import static org.apache.ignite.internal.network.serialization.DefaultType.CHAR_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.CHAR_BOXED;
-import static org.apache.ignite.internal.network.serialization.DefaultType.DATE;
-import static org.apache.ignite.internal.network.serialization.DefaultType.DECIMAL;
-import static org.apache.ignite.internal.network.serialization.DefaultType.DECIMAL_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.DOUBLE;
-import static org.apache.ignite.internal.network.serialization.DefaultType.DOUBLE_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.DOUBLE_BOXED;
-import static org.apache.ignite.internal.network.serialization.DefaultType.ENUM;
-import static org.apache.ignite.internal.network.serialization.DefaultType.ENUM_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.FLOAT;
-import static org.apache.ignite.internal.network.serialization.DefaultType.FLOAT_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.FLOAT_BOXED;
-import static org.apache.ignite.internal.network.serialization.DefaultType.HASH_MAP;
-import static org.apache.ignite.internal.network.serialization.DefaultType.HASH_SET;
-import static org.apache.ignite.internal.network.serialization.DefaultType.IGNITE_UUID;
-import static org.apache.ignite.internal.network.serialization.DefaultType.INT;
-import static org.apache.ignite.internal.network.serialization.DefaultType.INT_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.INT_BOXED;
-import static org.apache.ignite.internal.network.serialization.DefaultType.LINKED_HASH_MAP;
-import static org.apache.ignite.internal.network.serialization.DefaultType.LINKED_HASH_SET;
-import static org.apache.ignite.internal.network.serialization.DefaultType.LINKED_LIST;
-import static org.apache.ignite.internal.network.serialization.DefaultType.LONG;
-import static org.apache.ignite.internal.network.serialization.DefaultType.LONG_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.LONG_BOXED;
-import static org.apache.ignite.internal.network.serialization.DefaultType.NULL;
-import static org.apache.ignite.internal.network.serialization.DefaultType.OBJECT;
-import static org.apache.ignite.internal.network.serialization.DefaultType.OBJECT_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.SHORT;
-import static org.apache.ignite.internal.network.serialization.DefaultType.SHORT_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.SHORT_BOXED;
-import static org.apache.ignite.internal.network.serialization.DefaultType.SINGLETON_LIST;
-import static org.apache.ignite.internal.network.serialization.DefaultType.STRING;
-import static org.apache.ignite.internal.network.serialization.DefaultType.STRING_ARRAY;
-import static org.apache.ignite.internal.network.serialization.DefaultType.UUID;
-import static org.apache.ignite.internal.network.serialization.DefaultType.VOID;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.ARRAY_LIST;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.BIT_SET;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.BOOLEAN;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.BOOLEAN_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.BOOLEAN_BOXED;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.BYTE;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.BYTE_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.BYTE_BOXED;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.CHAR;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.CHAR_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.CHAR_BOXED;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.DATE;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.DECIMAL;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.DECIMAL_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.DOUBLE;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.DOUBLE_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.DOUBLE_BOXED;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.ENUM;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.ENUM_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.FLOAT;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.FLOAT_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.FLOAT_BOXED;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.HASH_MAP;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.HASH_SET;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.IGNITE_UUID;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.INT;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.INT_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.INT_BOXED;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.LINKED_HASH_MAP;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.LINKED_HASH_SET;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.LINKED_LIST;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.LONG;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.LONG_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.LONG_BOXED;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.NULL;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.OBJECT;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.OBJECT_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.SHORT;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.SHORT_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.SHORT_BOXED;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.SINGLETON_LIST;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.STRING;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.STRING_ARRAY;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.UUID;
+import static org.apache.ignite.internal.network.serialization.BuiltinType.VOID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerTest.java
new file mode 100644
index 0000000..f773ed5
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerTest.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.List;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link DefaultUserObjectMarshaller}.
+ */
+class DefaultUserObjectMarshallerTest {
+ private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();
+ private final ClassDescriptorFactory descriptorFactory = new ClassDescriptorFactory(descriptorRegistry);
+
+ private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
+
+ private static final int WRITE_REPLACE_INCREMENT = 1_000_000;
+ private static final int READ_RESOLVE_INCREMENT = 1_000;
+
+ @Test
+ void usesExactlyOneDescriptorWhenMarshallingExternalizable() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new SimpleExternalizable(42));
+
+ ClassDescriptor expectedDescriptor = descriptorRegistry.getDescriptor(SimpleExternalizable.class);
+ assertThat(expectedDescriptor, is(notNullValue()));
+ assertThat(marshalled.usedDescriptors(), is(equalTo(List.of(expectedDescriptor))));
+ }
+
+ @Test
+ void marshalsAndUnmarshalsExternalizable() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new SimpleExternalizable(42));
+
+ SimpleExternalizable unmarshalled = marshaller.unmarshal(marshalled.bytes());
+
+ assertThat(unmarshalled, is(notNullValue()));
+ assertThat(unmarshalled.intValue, is(42));
+ }
+
+ @Test
+ void appliesWriteReplaceOnExternalizable() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new ExternalizableWithWriteReplace(42));
+
+ SimpleExternalizable unmarshalled = marshaller.unmarshal(marshalled.bytes());
+
+ assertThat(unmarshalled.intValue, is(equalTo(42 + WRITE_REPLACE_INCREMENT)));
+ }
+
+ @Test
+ void appliesReadResolveOnExternalizable() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new ExternalizableWithReadResolve(42));
+
+ SimpleExternalizable unmarshalled = marshaller.unmarshal(marshalled.bytes());
+
+ assertThat(unmarshalled.intValue, is(equalTo(42 + READ_RESOLVE_INCREMENT)));
+ }
+
+ @Test
+ void appliesBothWriteReplaceAndReadResolveOnExternalizable() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new ExternalizableWithWriteReplaceReadResolve(42));
+
+ SimpleExternalizable unmarshalled = marshaller.unmarshal(marshalled.bytes());
+
+ assertThat(unmarshalled.intValue, is(equalTo(42 + WRITE_REPLACE_INCREMENT + READ_RESOLVE_INCREMENT)));
+ }
+
+ /**
+ * An {@link Externalizable} that does not have {@code writeReplace()}/{@code readResolve()} methods.
+ */
+ private static class SimpleExternalizable implements Externalizable {
+ int intValue;
+
+ public SimpleExternalizable() {
+ }
+
+ public SimpleExternalizable(int intValue) {
+ this.intValue = intValue;
+ }
+
+ @Override
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeInt(-intValue);
+ }
+
+ @Override
+ public void readExternal(ObjectInput in) throws IOException {
+ intValue = -in.readInt();
+ }
+ }
+
+ private static class ExternalizableWithWriteReplace extends SimpleExternalizable {
+ public ExternalizableWithWriteReplace() {
+ }
+
+ public ExternalizableWithWriteReplace(int intValue) {
+ super(intValue);
+ }
+
+ private Object writeReplace() {
+ return new ExternalizableWithWriteReplaceReadResolve(intValue + 1_000_000);
+ }
+ }
+
+ private static class ExternalizableWithReadResolve extends SimpleExternalizable {
+ public ExternalizableWithReadResolve() {
+ }
+
+ public ExternalizableWithReadResolve(int intValue) {
+ super(intValue);
+ }
+
+ private Object readResolve() {
+ return new ExternalizableWithWriteReplaceReadResolve(intValue + 1_000);
+ }
+ }
+
+ private static class ExternalizableWithWriteReplaceReadResolve extends SimpleExternalizable {
+ public ExternalizableWithWriteReplaceReadResolve() {
+ }
+
+ public ExternalizableWithWriteReplaceReadResolve(int intValue) {
+ super(intValue);
+ }
+
+ private Object writeReplace() {
+ return new ExternalizableWithWriteReplaceReadResolve(intValue + 1_000_000);
+ }
+
+ private Object readResolve() {
+ return new ExternalizableWithWriteReplaceReadResolve(intValue + 1_000);
+ }
+ }
+}