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 2022/01/10 12:59:05 UTC
[ignite-3] branch main updated: IGNITE-16164 Implement (un)marshalling of arbitrary objects (#530)
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 781122a IGNITE-16164 Implement (un)marshalling of arbitrary objects (#530)
781122a is described below
commit 781122a710652f0c0668178da9ba8ef45d64de7c
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Mon Jan 10 16:58:59 2022 +0400
IGNITE-16164 Implement (un)marshalling of arbitrary objects (#530)
---
modules/network/pom.xml | 6 +
.../network/message/FieldDescriptorMessage.java | 5 +
.../network/serialization/BuiltinType.java | 6 +-
.../serialization/ClassDescriptorFactory.java | 60 ++-
.../ClassDescriptorFactoryContext.java | 17 +-
.../serialization/ClassIndexedDescriptors.java | 50 ++
.../ValueWriter.java => FieldAccessor.java} | 27 +-
.../network/serialization/FieldAccessorImpl.java | 83 ++++
.../network/serialization/FieldDescriptor.java | 46 +-
.../PerSessionSerializationService.java | 15 +-
...ueWriter.java => SerializedStreamCommands.java} | 22 +-
.../SpecialSerializationMethodsImpl.java | 34 +-
.../marshal/ArbitraryObjectMarshaller.java | 82 ++++
.../marshal/BestEffortInstantiation.java | 56 +++
.../marshal/BuiltInContainerMarshallers.java | 159 ++++---
.../serialization/marshal/BuiltInMarshalling.java | 126 ++++--
.../marshal/BuiltInNonContainerMarshallers.java | 39 +-
.../marshal/DefaultUserObjectMarshaller.java | 349 +++++++++-----
.../marshal/ExternalizableMarshaller.java | 75 +++
.../{ValueWriter.java => Instantiation.java} | 28 +-
...alueWriter.java => InstantiationException.java} | 24 +-
.../serialization/marshal/MarshallingContext.java | 90 ++++
.../marshal/NoArgConstructorInstantiation.java | 50 ++
.../marshal/SerializableInstantiation.java | 115 +++++
...ackingMarshaller.java => TypedValueWriter.java} | 18 +-
.../marshal/UnmarshallingContext.java | 32 +-
...allingContext.java => UnsafeInstantiation.java} | 25 +-
.../network/serialization/marshal/ValueWriter.java | 7 +-
.../serialization/ClassDescriptorFactoryTest.java | 31 ++
.../marshal/BestEffortInstantiationTest.java | 97 ++++
...erObjectMarshallerWithArbitraryObjectsTest.java | 504 +++++++++++++++++++++
...efaultUserObjectMarshallerWithBuiltinsTest.java | 233 ++++++----
.../marshal/MarshallingContextTest.java | 86 ++++
.../marshal/NoArgConstructorInstantiationTest.java | 62 +++
.../marshal/SerializableInstantiationTest.java | 163 +++++++
.../marshal/UnsafeInstantiationTest.java | 53 +++
.../marshal/WithAccessibleNoArgConstructor.java} | 17 +-
.../marshal/WithPrivateNoArgConstructor.java} | 19 +-
.../marshal/WithoutNoArgConstructor.java} | 20 +-
39 files changed, 2452 insertions(+), 479 deletions(-)
diff --git a/modules/network/pom.xml b/modules/network/pom.xml
index c9ce704..c55a57e 100644
--- a/modules/network/pom.xml
+++ b/modules/network/pom.xml
@@ -108,6 +108,12 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+
<!-- Logging in tests -->
<dependency>
<groupId>org.slf4j</groupId>
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/message/FieldDescriptorMessage.java b/modules/network/src/main/java/org/apache/ignite/internal/network/message/FieldDescriptorMessage.java
index aa7a3ec..1409544 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/message/FieldDescriptorMessage.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/message/FieldDescriptorMessage.java
@@ -39,4 +39,9 @@ public interface FieldDescriptorMessage extends NetworkMessage {
* Field's class name.
*/
String className();
+
+ /**
+ * The name of The class in which this field is declared.
+ */
+ String declaringClassName();
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java
index 6ffc8c9..46d22e5 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltinType.java
@@ -31,6 +31,8 @@ import org.apache.ignite.lang.IgniteUuid;
/**
* Built-in types.
+ *
+ * <p>They share ID space with commands defined in {@link SerializedStreamCommands}.
*/
public enum BuiltinType {
BYTE(0, byte.class),
@@ -79,7 +81,9 @@ public enum BuiltinType {
LINKED_HASH_MAP(41, LinkedHashMap.class),
BIT_SET(42, BitSet.class),
NULL(43, Null.class),
- VOID(44, Void.class);
+ VOID(44, Void.class)
+ // 45 is REFERENCE command, see SerializedStreamCommands#REFERENCE
+ ;
/**
* Pre-defined descriptor id.
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 0732b4e..0a3a4a9 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
@@ -17,19 +17,24 @@
package org.apache.ignite.internal.network.serialization;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
import java.io.Externalizable;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.ignite.lang.IgniteException;
import org.jetbrains.annotations.Nullable;
@@ -207,26 +212,55 @@ public class ClassDescriptorFactory {
}
/**
- * Gets field descriptors of the class. If a field's type doesn't have an id yet, generates it.
+ * Gets field descriptors of the class in the correct order (see {@link #classFields(Class)}. If a field's type
+ * doesn't have an id yet, generates it.
*
* @param clazz Class.
* @return List of field descriptor.
*/
private List<FieldDescriptor> fields(Class<?> clazz) {
- if (clazz.getSuperclass() != Object.class) {
- // TODO: IGNITE-15945 add support for the inheritance
- throw new UnsupportedOperationException("IGNITE-15945");
+ List<Class<?>> lineage = lineage(clazz);
+
+ return lineage.stream()
+ .flatMap(this::classFields)
+ .collect(toList());
+ }
+
+ /**
+ * Returns the lineage (all the ancestors, from Object down the line, including the given class).
+ *
+ * @param clazz class from which to obtain lineage
+ * @return ancestors from Object down the line, plus the given class itself
+ */
+ private List<Class<?>> lineage(Class<?> clazz) {
+ List<Class<?>> classes = new ArrayList<>();
+
+ Class<?> currentClass = clazz;
+ while (currentClass != null) {
+ classes.add(currentClass);
+ currentClass = currentClass.getSuperclass();
}
+ Collections.reverse(classes);
+ return classes;
+ }
+
+ /**
+ * Returns 'serializable' (i.e. non-static non-transient) declared fields of the given class sorted lexicographically by their names.
+ *
+ * @param clazz class
+ * @return properly sorted fields
+ */
+ private Stream<FieldDescriptor> classFields(Class<?> clazz) {
return Arrays.stream(clazz.getDeclaredFields())
- .filter(field -> {
- int modifiers = field.getModifiers();
-
- // Ignore static and transient field.
- return !Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers);
- })
- .map(field -> new FieldDescriptor(field, context.getId(field.getType())))
- .collect(Collectors.toList());
+ .sorted(comparing(Field::getName))
+ .filter(field -> {
+ int modifiers = field.getModifiers();
+
+ // Ignore static and transient fields.
+ return !Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers);
+ })
+ .map(field -> new FieldDescriptor(field, context.getId(field.getType())));
}
/**
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 e0dc9ba..708aae9 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
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.Nullable;
/**
* Class descriptor factory context.
*/
-public class ClassDescriptorFactoryContext implements IdIndexedDescriptors {
+public class ClassDescriptorFactoryContext implements IdIndexedDescriptors, ClassIndexedDescriptors {
/** Quantity of descriptor ids reserved for the default descriptors. */
private static final int DEFAULT_DESCRIPTORS_OFFSET_COUNT = 1000;
@@ -93,6 +93,7 @@ public class ClassDescriptorFactoryContext implements IdIndexedDescriptors {
* @param clazz Class.
* @return Descriptor.
*/
+ @Override
@Nullable
public ClassDescriptor getDescriptor(Class<?> clazz) {
Integer descriptorId = idMap.get(clazz);
@@ -105,20 +106,6 @@ public class ClassDescriptorFactoryContext implements IdIndexedDescriptors {
}
/**
- * Gets a descriptor by the class or throws an exception if no such class is known.
- *
- * @param clazz Class.
- * @return Descriptor.
- */
- public ClassDescriptor getRequiredDescriptor(Class<?> clazz) {
- ClassDescriptor descriptor = getDescriptor(clazz);
- if (descriptor == null) {
- throw new IllegalStateException("No descriptor exists for " + clazz);
- }
- return descriptor;
- }
-
- /**
* Returns a descriptor for a built-in type.
*
* @param builtinType built-in type for lookup
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassIndexedDescriptors.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassIndexedDescriptors.java
new file mode 100644
index 0000000..4bef0d7
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassIndexedDescriptors.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Container of {@link ClassDescriptor}s indexed by their classes.
+ */
+public interface ClassIndexedDescriptors {
+ /**
+ * Returns a descriptor by class or throws an exception if no such descriptor is known.
+ *
+ * @param clazz for lookup
+ * @return descriptor by class
+ */
+ @Nullable
+ ClassDescriptor getDescriptor(Class<?> clazz);
+
+ /**
+ * Returns a descriptor by class or throws an exception if no such descriptor is known.
+ *
+ * @param clazz for lookup
+ * @return descriptor by class
+ */
+ default ClassDescriptor getRequiredDescriptor(Class<?> clazz) {
+ ClassDescriptor descriptor = getDescriptor(clazz);
+
+ if (descriptor == null) {
+ throw new IllegalStateException("Did not find a descriptor by class=" + clazz);
+ }
+
+ return descriptor;
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldAccessor.java
similarity index 61%
copy from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
copy to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldAccessor.java
index d3d75b9..34c4806 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldAccessor.java
@@ -15,22 +15,25 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.network.serialization.marshal;
-
-import java.io.DataOutput;
-import java.io.IOException;
+package org.apache.ignite.internal.network.serialization;
/**
- * Knows how to write a value to a {@link DataOutput}.
+ * Accessor for a specific field.
*/
-interface ValueWriter<T> {
+public interface FieldAccessor {
+ /**
+ * Returns the bound field value of the given object.
+ *
+ * @param target target object
+ * @return the bound field value of the given object
+ */
+ Object get(Object target);
+
/**
- * Writes the given value to a {@link DataOutput}.
+ * Sets the bound field value on the given object.
*
- * @param value value to write
- * @param output where to write to
- * @throws IOException if an I/O problem occurs
- * @throws MarshalException if another problem occurs
+ * @param target target object
+ * @param fieldValue value to set
*/
- void write(T value, DataOutput output) throws IOException, MarshalException;
+ void set(Object target, Object fieldValue);
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldAccessorImpl.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldAccessorImpl.java
new file mode 100644
index 0000000..1346397
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldAccessorImpl.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+/**
+ * {@link FieldAccessor} implementation.
+ */
+class FieldAccessorImpl implements FieldAccessor {
+ private final Field field;
+ private final VarHandle varHandle;
+
+ FieldAccessorImpl(FieldDescriptor descriptor) {
+ field = findField(descriptor);
+ field.setAccessible(true);
+
+ varHandle = varHandleFrom(field);
+ }
+
+ private static Field findField(FieldDescriptor fieldDescriptor) {
+ try {
+ return fieldDescriptor.declaringClass().getDeclaredField(fieldDescriptor.name());
+ } catch (NoSuchFieldException e) {
+ throw new ReflectionException("Cannot find field", e);
+ }
+ }
+
+ private static VarHandle varHandleFrom(Field field) {
+ try {
+ MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(field.getDeclaringClass(), MethodHandles.lookup());
+ return lookup.unreflectVarHandle(field);
+ } catch (ReflectiveOperationException e) {
+ throw new ReflectionException("Cannot get a field VarHandle", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object get(Object target) {
+ return varHandle.get(target);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void set(Object target, Object fieldValue) {
+ if (isFieldFinal()) {
+ setViaField(target, fieldValue);
+ } else {
+ varHandle.set(target, fieldValue);
+ }
+ }
+
+ private boolean isFieldFinal() {
+ return Modifier.isFinal(field.getModifiers());
+ }
+
+ private void setViaField(Object target, Object fieldValue) {
+ try {
+ field.set(target, fieldValue);
+ } catch (IllegalAccessException e) {
+ throw new ReflectionException("Cannot set a value", e);
+ }
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldDescriptor.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldDescriptor.java
index ff72f20..fe86f24 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldDescriptor.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldDescriptor.java
@@ -27,13 +27,11 @@ public class FieldDescriptor {
/**
* Name of the field.
*/
- @NotNull
private final String name;
/**
* Type of the field.
*/
- @NotNull
private final Class<?> clazz;
/**
@@ -42,23 +40,37 @@ public class FieldDescriptor {
private final int typeDescriptorId;
/**
+ * The class in which the field is declared.
+ */
+ private final Class<?> declaringClass;
+
+ /**
+ * Accessor for accessing this field.
+ */
+ private final FieldAccessor accessor;
+
+ /**
* Constructor.
*/
- public FieldDescriptor(@NotNull Field field, int typeDescriptorId) {
- this(field.getName(), field.getType(), typeDescriptorId);
+ public FieldDescriptor(Field field, int typeDescriptorId) {
+ this(field.getName(), field.getType(), typeDescriptorId, field.getDeclaringClass());
}
/**
* Constructor.
*
- * @param fieldName .
- * @param fieldClazz .
- * @param typeDescriptorId .
+ * @param fieldName field name
+ * @param fieldClazz type of the field
+ * @param typeDescriptorId ID of the descriptor corresponding to field type
+ * @param declaringClass the class in which the field if declared
*/
- public FieldDescriptor(@NotNull String fieldName, @NotNull Class<?> fieldClazz, int typeDescriptorId) {
+ public FieldDescriptor(String fieldName, Class<?> fieldClazz, int typeDescriptorId, Class<?> declaringClass) {
this.name = fieldName;
this.clazz = fieldClazz;
this.typeDescriptorId = typeDescriptorId;
+ this.declaringClass = declaringClass;
+
+ accessor = new FieldAccessorImpl(this);
}
/**
@@ -89,4 +101,22 @@ public class FieldDescriptor {
public int typeDescriptorId() {
return typeDescriptorId;
}
+
+ /**
+ * Returns the class in which the field is declared.
+ *
+ * @return the class in which the field is declared
+ */
+ public Class<?> declaringClass() {
+ return declaringClass;
+ }
+
+ /**
+ * Returns {@link FieldAccessor} for this field.
+ *
+ * @return {@link FieldAccessor} for this field
+ */
+ public FieldAccessor accessor() {
+ return accessor;
+ }
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
index 53315f6..d8a546c 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
@@ -114,6 +114,7 @@ public class PerSessionSerializationService {
.name(d.name())
.typeDescriptorId(d.typeDescriptorId())
.className(d.clazz().getName())
+ .declaringClassName(d.declaringClass().getName())
.build();
})
.collect(toList());
@@ -162,11 +163,9 @@ public class PerSessionSerializationService {
private ClassDescriptor messageToMergedClassDescriptor(ClassDescriptorMessage clsMsg) {
ClassDescriptor localDescriptor = serializationService.getClassDescriptor(clsMsg.className());
- List<FieldDescriptor> remoteFields = clsMsg.fields().stream().map(fieldMsg -> {
- int typeDescriptorId = fieldMsg.typeDescriptorId();
-
- return new FieldDescriptor(fieldMsg.name(), getClass(typeDescriptorId, fieldMsg.className()), typeDescriptorId);
- }).collect(toList());
+ List<FieldDescriptor> remoteFields = clsMsg.fields().stream()
+ .map(this::fieldDescriptorFromMessage)
+ .collect(toList());
SerializationType serializationType = SerializationType.getByValue(clsMsg.serializationType());
@@ -187,6 +186,12 @@ public class PerSessionSerializationService {
return mergeDescriptor(localDescriptor, remoteDescriptor);
}
+ private FieldDescriptor fieldDescriptorFromMessage(FieldDescriptorMessage fieldMsg) {
+ int typeDescriptorId = fieldMsg.typeDescriptorId();
+ Class<?> declaringClass = serializationService.getClassDescriptor(fieldMsg.declaringClassName()).clazz();
+ return new FieldDescriptor(fieldMsg.name(), getClass(typeDescriptorId, fieldMsg.className()), typeDescriptorId, declaringClass);
+ }
+
private ClassDescriptor mergeDescriptor(ClassDescriptor localDescriptor, ClassDescriptor remoteDescriptor) {
// TODO: IGNITE-15948 Handle class structure changes
return remoteDescriptor;
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializedStreamCommands.java
similarity index 60%
copy from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
copy to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializedStreamCommands.java
index d3d75b9..1299519 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializedStreamCommands.java
@@ -15,22 +15,18 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.network.serialization.marshal;
-
-import java.io.DataOutput;
-import java.io.IOException;
+package org.apache.ignite.internal.network.serialization;
/**
- * Knows how to write a value to a {@link DataOutput}.
+ * Lists commands used in the serialized stream.
+ * Command IDs share space with IDs of {@link ClassDescriptor}s (most importantly, those that are defined in {@link BuiltinType}.
*/
-interface ValueWriter<T> {
+public class SerializedStreamCommands {
/**
- * Writes the given value to a {@link DataOutput}.
- *
- * @param value value to write
- * @param output where to write to
- * @throws IOException if an I/O problem occurs
- * @throws MarshalException if another problem occurs
+ * Reference: an object that was already seen in the graph, so we relate to it by its ID instead of serializing it again.
*/
- void write(T value, DataOutput output) throws IOException, MarshalException;
+ public static final int REFERENCE = 45;
+
+ private SerializedStreamCommands() {
+ }
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java
index 4384307..553c9dc 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java
@@ -49,53 +49,41 @@ class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
private static MethodHandle writeReplaceHandle(ClassDescriptor descriptor) {
Method writeReplaceMethod = findWriteReplaceMethod(descriptor);
+ return unreflect(writeReplaceMethod, MethodType.methodType(Object.class, Object.class), descriptor);
+ }
+
+ private static MethodHandle unreflect(Method method, MethodType methodType, ClassDescriptor descriptor) {
try {
- return MethodHandles.lookup()
- .unreflect(writeReplaceMethod)
- .asType(MethodType.methodType(Object.class, Object.class));
+ return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
+ .unreflect(method)
+ .asType(methodType);
} catch (IllegalAccessException e) {
- throw new ReflectionException("writeReplace() cannot be unreflected", e);
+ throw new ReflectionException("Cannot unreflect", e);
}
}
private static Method findWriteReplaceMethod(ClassDescriptor descriptor) {
- Method writeReplaceMethod;
try {
- writeReplaceMethod = descriptor.clazz().getDeclaredMethod("writeReplace");
+ return 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);
- }
+ return unreflect(readResolveMethod, MethodType.methodType(Object.class, Object.class), descriptor);
}
private static Method findReadResolveMethod(ClassDescriptor descriptor) {
- Method readResolveMethod;
try {
- readResolveMethod = descriptor.clazz().getDeclaredMethod("readResolve");
+ return 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;
}
/** {@inheritDoc} */
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java
new file mode 100644
index 0000000..cddc9cb
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ArbitraryObjectMarshaller.java
@@ -0,0 +1,82 @@
+/*
+ * 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.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.apache.ignite.internal.network.serialization.ClassIndexedDescriptors;
+import org.apache.ignite.internal.network.serialization.FieldDescriptor;
+
+/**
+ * (Un)marshals arbitrary objects (that is, objects that are not built-in nor serializable/externalizable).
+ */
+class ArbitraryObjectMarshaller {
+ private final TypedValueWriter valueWriter;
+ private final ValueReader<Object> valueReader;
+
+ private final Instantiation instantiation;
+
+ ArbitraryObjectMarshaller(ClassIndexedDescriptors descriptors, TypedValueWriter valueWriter, ValueReader<Object> valueReader) {
+ this.valueWriter = valueWriter;
+ this.valueReader = valueReader;
+
+ instantiation = new BestEffortInstantiation(
+ new NoArgConstructorInstantiation(),
+ new SerializableInstantiation(descriptors),
+ new UnsafeInstantiation()
+ );
+ }
+
+ void writeArbitraryObject(Object object, ClassDescriptor descriptor, DataOutput output, MarshallingContext context)
+ throws MarshalException, IOException {
+ context.addUsedDescriptor(descriptor);
+
+ for (FieldDescriptor fieldDescriptor : descriptor.fields()) {
+ writeField(object, fieldDescriptor, output, context);
+ }
+ }
+
+ private void writeField(Object object, FieldDescriptor fieldDescriptor, DataOutput output, MarshallingContext context)
+ throws MarshalException, IOException {
+ Object fieldValue = fieldDescriptor.accessor().get(object);
+
+ valueWriter.write(fieldValue, fieldDescriptor.clazz(), output, context);
+ }
+
+ Object preInstantiateArbitraryObject(ClassDescriptor descriptor) throws UnmarshalException {
+ try {
+ return instantiation.newInstance(descriptor.clazz());
+ } catch (InstantiationException e) {
+ throw new UnmarshalException("Cannot instantiate " + descriptor.clazz(), e);
+ }
+ }
+
+ void fillArbitraryObjectFrom(DataInput input, Object object, ClassDescriptor descriptor, UnmarshallingContext context)
+ throws IOException, UnmarshalException {
+ for (FieldDescriptor fieldDescriptor : descriptor.fields()) {
+ Object fieldValue = valueReader.read(input, context);
+ setFieldValue(object, fieldDescriptor, fieldValue);
+ }
+ }
+
+ private void setFieldValue(Object target, FieldDescriptor fieldDescriptor, Object value) {
+ fieldDescriptor.accessor().set(target, value);
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BestEffortInstantiation.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BestEffortInstantiation.java
new file mode 100644
index 0000000..c741a72
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BestEffortInstantiation.java
@@ -0,0 +1,56 @@
+/*
+ * 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.List;
+
+/**
+ * Instantiation strategy that delegates to a list of instantiation strategies and uses the first one that
+ * announces support for instantiating a given class.
+ */
+class BestEffortInstantiation implements Instantiation {
+ private final List<Instantiation> delegates;
+
+ BestEffortInstantiation(Instantiation... delegates) {
+ this.delegates = List.of(delegates);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean supports(Class<?> objectClass) {
+ for (Instantiation delegate : delegates) {
+ if (delegate.supports(objectClass)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object newInstance(Class<?> objectClass) throws InstantiationException {
+ for (Instantiation delegate : delegates) {
+ if (delegate.supports(objectClass)) {
+ return delegate.newInstance(objectClass);
+ }
+ }
+
+ throw new InstantiationException("No delegate supports " + objectClass);
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
index 80f710e..2f9a8d7 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
@@ -32,7 +32,6 @@ import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.function.IntFunction;
import org.apache.ignite.internal.network.serialization.ClassDescriptor;
@@ -47,7 +46,7 @@ class BuiltInContainerMarshallers {
*/
private final Map<Class<?>, IntFunction<? extends Collection<?>>> mutableBuiltInCollectionFactories = Map.of(
ArrayList.class, ArrayList::new,
- LinkedList.class, len -> new LinkedList<>(),
+ LinkedList.class, size -> new LinkedList<>(),
HashSet.class, HashSet::new,
LinkedHashSet.class, LinkedHashSet::new
);
@@ -62,29 +61,36 @@ class BuiltInContainerMarshallers {
LinkedHashMap.class, LinkedHashMap::new
);
- private final TrackingMarshaller trackingMarshaller;
+ /**
+ * Used to write elements.
+ */
+ private final ValueWriter<?> elementWriter;
- BuiltInContainerMarshallers(TrackingMarshaller trackingMarshaller) {
- this.trackingMarshaller = trackingMarshaller;
+ BuiltInContainerMarshallers(ValueWriter<?> elementWriter) {
+ this.elementWriter = elementWriter;
}
- Set<ClassDescriptor> writeGenericRefArray(Object[] array, ClassDescriptor arrayDescriptor, DataOutput output)
+ void writeGenericRefArray(Object[] array, ClassDescriptor arrayDescriptor, DataOutput output, MarshallingContext context)
throws IOException, MarshalException {
output.writeUTF(array.getClass().getComponentType().getName());
- return writeCollection(Arrays.asList(array), arrayDescriptor, output);
+ writeCollection(Arrays.asList(array), arrayDescriptor, output, context);
+ }
+
+ <T> T[] preInstantiateGenericRefArray(DataInput input) throws IOException {
+ return BuiltInMarshalling.preInstantiateGenericRefArray(input);
}
- <T> T[] readGenericRefArray(DataInput input, ValueReader<T> elementReader, UnmarshallingContext context)
+ <T> void fillGenericRefArray(DataInput input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
throws IOException, UnmarshalException {
- return BuiltInMarshalling.readGenericRefArray(input, elementReader, context);
+ BuiltInMarshalling.fillGenericRefArray(input, array, elementReader, context);
}
- Set<ClassDescriptor> writeBuiltInCollection(Collection<?> object, ClassDescriptor descriptor, DataOutput output)
+ void writeBuiltInCollection(Collection<?> object, ClassDescriptor descriptor, DataOutput output, MarshallingContext context)
throws IOException, MarshalException {
if (supportsAsMutableBuiltInCollection(descriptor)) {
- return writeCollection(object, descriptor, output);
+ writeCollection(object, descriptor, output, context);
} else if (descriptor.isSingletonList()) {
- return writeSingletonList((List<?>) object, descriptor, output);
+ writeSingletonList((List<?>) object, descriptor, output, context);
} else {
throw new IllegalStateException("Marshalling of " + descriptor.clazz() + " is not supported, but it's marked as a built-in");
}
@@ -102,49 +108,35 @@ class BuiltInContainerMarshallers {
return mutableBuiltInCollectionFactories.containsKey(descriptor.clazz());
}
- private Set<ClassDescriptor> writeCollection(Collection<?> collection, ClassDescriptor collectionDescriptor, DataOutput output)
- throws IOException, MarshalException {
- Set<ClassDescriptor> usedDescriptors = new HashSet<>();
- usedDescriptors.add(collectionDescriptor);
-
- BuiltInMarshalling.writeCollection(collection, output, writerAddingUsedDescriptor(usedDescriptors));
+ private void writeCollection(
+ Collection<?> collection,
+ ClassDescriptor collectionDescriptor,
+ DataOutput output,
+ MarshallingContext context
+ ) throws IOException, MarshalException {
+ context.addUsedDescriptor(collectionDescriptor);
- return usedDescriptors;
+ BuiltInMarshalling.writeCollection(collection, output, valueWriter(), context);
}
- private <T> ValueWriter<T> writerAddingUsedDescriptor(Set<ClassDescriptor> usedDescriptors) {
- return (elem, out) -> {
- Set<ClassDescriptor> elementDescriptors = trackingMarshaller.marshal(elem, out);
- usedDescriptors.addAll(elementDescriptors);
- };
+ @SuppressWarnings("unchecked")
+ private <T> ValueWriter<T> valueWriter() {
+ return (ValueWriter<T>) elementWriter;
}
- private Set<ClassDescriptor> writeSingletonList(List<?> list, ClassDescriptor listDescriptor, DataOutput output)
+ private void writeSingletonList(List<?> list, ClassDescriptor listDescriptor, DataOutput output, MarshallingContext context)
throws MarshalException, IOException {
assert list.size() == 1;
Object element = list.get(0);
- Set<ClassDescriptor> usedDescriptors = new HashSet<>();
- usedDescriptors.add(listDescriptor);
+ context.addUsedDescriptor(listDescriptor);
- Set<ClassDescriptor> descriptorsFromElement = trackingMarshaller.marshal(element, output);
- usedDescriptors.addAll(descriptorsFromElement);
-
- return usedDescriptors;
+ valueWriter().write(element, output, context);
}
@SuppressWarnings("unchecked")
- <T, C extends Collection<T>> C readBuiltInCollection(
- ClassDescriptor collectionDescriptor,
- ValueReader<T> elementReader,
- DataInput input,
- UnmarshallingContext context
- ) throws UnmarshalException, IOException {
- if (collectionDescriptor.isSingletonList()) {
- return (C) singletonList(elementReader.read(input, context));
- }
-
+ private <T, C extends Collection<T>> IntFunction<C> requiredCollectionFactory(ClassDescriptor collectionDescriptor) {
IntFunction<C> collectionFactory = (IntFunction<C>) mutableBuiltInCollectionFactories.get(collectionDescriptor.clazz());
if (collectionFactory == null) {
@@ -152,39 +144,89 @@ class BuiltInContainerMarshallers {
+ " even though it is marked as a built-in");
}
- return BuiltInMarshalling.readCollection(input, collectionFactory, elementReader, context);
+ return collectionFactory;
+ }
+
+ Object preInstantiateBuiltInMutableCollection(ClassDescriptor collectionDescriptor, DataInput input, UnmarshallingContext context)
+ throws IOException {
+ // TODO: IGNITE-16229 - proper immutable collections unmarshalling?
+ if (collectionDescriptor.isSingletonList()) {
+ return singletonList(null);
+ }
+
+ return preInstantiateNonSingletonCollection(collectionDescriptor, input, context);
}
- Set<ClassDescriptor> writeBuiltInMap(Map<?, ?> map, ClassDescriptor mapDescriptor, DataOutput output)
+ private <T, C extends Collection<T>> C preInstantiateNonSingletonCollection(
+ ClassDescriptor collectionDescriptor,
+ DataInput input,
+ UnmarshallingContext context
+ ) throws IOException {
+ IntFunction<C> collectionFactory = requiredCollectionFactory(collectionDescriptor);
+
+ context.markSource();
+
+ C collection = BuiltInMarshalling.preInstantiateCollection(input, collectionFactory);
+
+ context.resetSourceToMark();
+
+ return collection;
+ }
+
+ <T, C extends Collection<T>> void fillBuiltInCollectionFrom(
+ DataInput input,
+ C collection,
+ ClassDescriptor collectionDescriptor,
+ ValueReader<T> elementReader,
+ UnmarshallingContext context
+ ) throws UnmarshalException, IOException {
+ // TODO: IGNITE-16229 - proper immutable collections unmarshalling?
+ if (collectionDescriptor.isSingletonList()) {
+ BuiltInMarshalling.fillSingletonCollectionFrom(input, collection, elementReader, context);
+ return;
+ }
+
+ BuiltInMarshalling.fillCollectionFrom(input, collection, elementReader, context);
+ }
+
+ void writeBuiltInMap(Map<?, ?> map, ClassDescriptor mapDescriptor, DataOutput output, MarshallingContext context)
throws MarshalException, IOException {
if (!supportsAsBuiltInMap(mapDescriptor)) {
throw new IllegalStateException("Marshalling of " + mapDescriptor.clazz() + " is not supported, but it's marked as a built-in");
}
- Set<ClassDescriptor> usedDescriptors = new HashSet<>();
- usedDescriptors.add(mapDescriptor);
+ context.addUsedDescriptor(mapDescriptor);
BuiltInMarshalling.writeMap(
map,
output,
- writerAddingUsedDescriptor(usedDescriptors),
- writerAddingUsedDescriptor(usedDescriptors)
+ valueWriter(),
+ valueWriter(),
+ context
);
-
- return usedDescriptors;
}
private boolean supportsAsBuiltInMap(ClassDescriptor mapDescriptor) {
return mutableBuiltInMapFactories.containsKey(mapDescriptor.clazz());
}
- <K, V, M extends Map<K, V>> M readBuiltInMap(
+ <K, V, M extends Map<K, V>> M preInstantiateBuiltInMutableMap(
ClassDescriptor mapDescriptor,
- ValueReader<K> keyReader,
- ValueReader<V> valueReader,
DataInput input,
UnmarshallingContext context
- ) throws UnmarshalException, IOException {
+ ) throws IOException {
+ IntFunction<M> mapFactory = requiredMapFactory(mapDescriptor);
+
+ context.markSource();
+
+ M map = BuiltInMarshalling.preInstantiateMap(input, mapFactory);
+
+ context.resetSourceToMark();
+
+ return map;
+ }
+
+ private <K, V, M extends Map<K, V>> IntFunction<M> requiredMapFactory(ClassDescriptor mapDescriptor) {
@SuppressWarnings("unchecked")
IntFunction<M> mapFactory = (IntFunction<M>) mutableBuiltInMapFactories.get(mapDescriptor.clazz());
@@ -192,7 +234,16 @@ class BuiltInContainerMarshallers {
throw new IllegalStateException("Did not find a map factory for " + mapDescriptor.clazz()
+ " even though it is marked as a built-in");
}
+ return mapFactory;
+ }
- return BuiltInMarshalling.readMap(input, mapFactory, keyReader, valueReader, context);
+ <K, V, M extends Map<K, V>> void fillBuiltInMapFrom(
+ DataInput input,
+ M map,
+ ValueReader<K> keyReader,
+ ValueReader<V> valueReader,
+ UnmarshallingContext context
+ ) throws UnmarshalException, IOException {
+ BuiltInMarshalling.fillMapFrom(input, map, keyReader, valueReader, context);
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
index 68aea24..cb12b8a 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
@@ -17,10 +17,13 @@
package org.apache.ignite.internal.network.serialization.marshal;
+import static java.util.Collections.singletonList;
+
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.lang.reflect.Array;
+import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.BitSet;
import java.util.Collection;
@@ -35,17 +38,28 @@ import org.jetbrains.annotations.NotNull;
* Built-in types marshalling.
*/
class BuiltInMarshalling {
- private static final ValueWriter<String> stringWriter = BuiltInMarshalling::writeString;
+ private static final ValueWriter<String> stringWriter = (obj, out, ctx) -> writeString(obj, out);
private static final IntFunction<String[]> stringArrayFactory = String[]::new;
private static final ValueReader<String> stringReader = (in, ctx) -> readString(in);
- private static final ValueWriter<BigDecimal> bigDecimalWriter = BuiltInMarshalling::writeBigDecimal;
+ private static final ValueWriter<BigDecimal> bigDecimalWriter = (obj, out, ctx) -> writeBigDecimal(obj, out);
private static final IntFunction<BigDecimal[]> bigDecimalArrayFactory = BigDecimal[]::new;
private static final ValueReader<BigDecimal> bigDecimalReader = (in, ctx) -> readBigDecimal(in);
- private static final ValueWriter<Enum<?>> enumWriter = BuiltInMarshalling::writeEnum;
+ private static final ValueWriter<Enum<?>> enumWriter = (obj, out, ctx) -> writeEnum(obj, out);
private static final ValueReader<Enum<?>> enumReader = (in, ctx) -> readEnum(in);
+ private static final Field singletonListElementField;
+
+ static {
+ try {
+ singletonListElementField = singletonList(null).getClass().getDeclaredField("element");
+ singletonListElementField.setAccessible(true);
+ } catch (ReflectiveOperationException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
static void writeString(String string, DataOutput output) throws IOException {
output.writeUTF(string);
}
@@ -54,7 +68,7 @@ class BuiltInMarshalling {
return input.readUTF();
}
- static Object readBareObject(DataInput input) {
+ static Object readBareObject(@SuppressWarnings("unused") DataInput input) {
return new Object();
}
@@ -256,52 +270,69 @@ class BuiltInMarshalling {
}
}
- static <T> void writeRefArray(T[] array, DataOutput output, ValueWriter<T> valueWriter) throws IOException, MarshalException {
+ static <T> void writeRefArray(T[] array, DataOutput output, ValueWriter<T> valueWriter, MarshallingContext context)
+ throws IOException, MarshalException {
output.writeInt(array.length);
for (T object : array) {
- valueWriter.write(object, output);
+ valueWriter.write(object, output, context);
}
}
static <T> T[] readRefArray(DataInput input, IntFunction<T[]> arrayFactory, ValueReader<T> valueReader, UnmarshallingContext context)
throws IOException, UnmarshalException {
int length = input.readInt();
+
T[] array = arrayFactory.apply(length);
- for (int i = 0; i < length; i++) {
- array[i] = valueReader.read(input, context);
- }
+ fillRefArrayFrom(input, array, valueReader, context);
+
return array;
}
- static <T> T[] readGenericRefArray(DataInput input, ValueReader<T> elementReader, UnmarshallingContext context)
+ private static <T> void fillRefArrayFrom(DataInput input, T[] array, ValueReader<T> valueReader, UnmarshallingContext context)
throws IOException, UnmarshalException {
+ for (int i = 0; i < array.length; i++) {
+ array[i] = valueReader.read(input, context);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> IntFunction<T[]> readTypeAndCreateArrayFactory(DataInput input) throws IOException {
String componentClassName = input.readUTF();
Class<T> componentType = classByName(componentClassName, "component");
- @SuppressWarnings("unchecked")
- IntFunction<T[]> arrayFactory = len -> (T[]) Array.newInstance(componentType, len);
- return readRefArray(input, arrayFactory, elementReader, context);
+ return len -> (T[]) Array.newInstance(componentType, len);
}
+ static <T> T[] preInstantiateGenericRefArray(DataInput input) throws IOException {
+ IntFunction<T[]> arrayFactory = readTypeAndCreateArrayFactory(input);
+ int length = input.readInt();
+ return arrayFactory.apply(length);
+ }
+
+ static <T> void fillGenericRefArray(DataInput input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
+ throws IOException, UnmarshalException {
+ fillRefArrayFrom(input, array, elementReader, context);
+ }
- static void writeStringArray(String[] array, DataOutput output) throws IOException, MarshalException {
- writeRefArray(array, output, stringWriter);
+ static void writeStringArray(String[] array, DataOutput output, MarshallingContext context) throws IOException, MarshalException {
+ writeRefArray(array, output, stringWriter, context);
}
static String[] readStringArray(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
return readRefArray(input, stringArrayFactory, stringReader, context);
}
- static void writeBigDecimalArray(BigDecimal[] array, DataOutput output) throws IOException, MarshalException {
- writeRefArray(array, output, bigDecimalWriter);
+ static void writeBigDecimalArray(BigDecimal[] array, DataOutput output, MarshallingContext context)
+ throws IOException, MarshalException {
+ writeRefArray(array, output, bigDecimalWriter, context);
}
static BigDecimal[] readBigDecimalArray(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
return readRefArray(input, bigDecimalArrayFactory, bigDecimalReader, context);
}
- static void writeEnumArray(Enum<?>[] array, DataOutput output) throws IOException, MarshalException {
+ static void writeEnumArray(Enum<?>[] array, DataOutput output, MarshallingContext context) throws IOException, MarshalException {
output.writeUTF(array.getClass().getComponentType().getName());
- writeRefArray(array, output, enumWriter);
+ writeRefArray(array, output, enumWriter, context);
}
static Enum<?>[] readEnumArray(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
@@ -310,50 +341,79 @@ class BuiltInMarshalling {
return readRefArray(input, len -> (Enum<?>[]) Array.newInstance(enumClass, len), enumReader, context);
}
- static <T> void writeCollection(Collection<T> collection, DataOutput output, ValueWriter<T> valueWriter)
+ static <T> void writeCollection(Collection<T> collection, DataOutput output, ValueWriter<T> valueWriter, MarshallingContext context)
throws IOException, MarshalException {
output.writeInt(collection.size());
+
for (T object : collection) {
- valueWriter.write(object, output);
+ valueWriter.write(object, output, context);
}
}
- static <T, C extends Collection<T>> C readCollection(
+ static <T, C extends Collection<T>> void fillCollectionFrom(
DataInput input,
- IntFunction<C> collectionFactory,
+ C collection,
ValueReader<T> valueReader,
UnmarshallingContext context
) throws IOException, UnmarshalException {
int length = input.readInt();
- C collection = collectionFactory.apply(length);
+
for (int i = 0; i < length; i++) {
collection.add(valueReader.read(input, context));
}
- return collection;
}
- static <K, V> void writeMap(Map<K, V> map, DataOutput output, ValueWriter<K> keyWriter, ValueWriter<V> valueWriter)
- throws IOException, MarshalException {
+ static <T, C extends Collection<T>> C preInstantiateCollection(DataInput input, IntFunction<C> collectionFactory) throws IOException {
+ int length = input.readInt();
+ return collectionFactory.apply(length);
+ }
+
+ static <T, C extends Collection<T>> void fillSingletonCollectionFrom(
+ DataInput input,
+ C collection,
+ ValueReader<T> elementReader,
+ UnmarshallingContext context
+ ) throws IOException, UnmarshalException {
+ T element = elementReader.read(input, context);
+
+ try {
+ singletonListElementField.set(collection, element);
+ } catch (ReflectiveOperationException e) {
+ throw new UnmarshalException("Cannot set field value", e);
+ }
+ }
+
+ static <K, V> void writeMap(
+ Map<K, V> map,
+ DataOutput output,
+ ValueWriter<K> keyWriter,
+ ValueWriter<V> valueWriter,
+ MarshallingContext context
+ ) throws IOException, MarshalException {
output.writeInt(map.size());
+
for (Map.Entry<K, V> entry : map.entrySet()) {
- keyWriter.write(entry.getKey(), output);
- valueWriter.write(entry.getValue(), output);
+ keyWriter.write(entry.getKey(), output, context);
+ valueWriter.write(entry.getValue(), output, context);
}
}
- static <K, V, M extends Map<K, V>> M readMap(
+ static <K, V, M extends Map<K, V>> void fillMapFrom(
DataInput input,
- IntFunction<M> mapFactory,
+ M map,
ValueReader<K> keyReader,
ValueReader<V> valueReader,
UnmarshallingContext context
) throws IOException, UnmarshalException {
int length = input.readInt();
- M map = mapFactory.apply(length);
for (int i = 0; i < length; i++) {
map.put(keyReader.read(input, context), valueReader.read(input, context));
}
- return map;
+ }
+
+ static <K, V, M extends Map<K, V>> M preInstantiateMap(DataInput input, IntFunction<M> mapFactory) throws IOException {
+ int length = input.readInt();
+ return mapFactory.apply(length);
}
static void writeBitSet(BitSet object, DataOutput output) throws IOException {
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java
index aa3db82..7cd0758 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java
@@ -25,9 +25,9 @@ import java.util.BitSet;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
-import java.util.Set;
import java.util.UUID;
import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.apache.ignite.internal.network.serialization.Null;
import org.apache.ignite.lang.IgniteUuid;
/**
@@ -66,6 +66,7 @@ class BuiltInNonContainerMarshallers {
addSingle(map, Enum.class, BuiltInMarshalling::writeEnum, BuiltInMarshalling::readEnum);
addSingle(map, Enum[].class, BuiltInMarshalling::writeEnumArray, BuiltInMarshalling::readEnumArray);
addSingle(map, BitSet.class, BuiltInMarshalling::writeBitSet, BuiltInMarshalling::readBitSet);
+ addSingle(map, Null.class, (obj, output) -> {}, input -> null);
addSingle(map, Void.class, (obj, output) -> {}, input -> null);
return Map.copyOf(map);
@@ -85,25 +86,29 @@ class BuiltInNonContainerMarshallers {
private static <T> void addSingle(
Map<Class<?>, BuiltInMarshaller<?>> map,
Class<T> objectClass,
- ValueWriter<T> writer,
+ ContextlessValueWriter<T> writer,
ContextlessValueReader<T> reader
) {
- addSingle(map, objectClass, writer, contextless(reader));
+ addSingle(map, objectClass, contextless(writer), contextless(reader));
}
private static <T> void addPrimitiveAndWrapper(
Map<Class<?>, BuiltInMarshaller<?>> map,
Class<?> primitiveClass,
Class<T> wrapperClass,
- ValueWriter<T> writer,
+ ContextlessValueWriter<T> writer,
ContextlessValueReader<T> reader
) {
- BuiltInMarshaller<T> builtInMarshaller = builtInMarshaller(wrapperClass, writer, contextless(reader));
+ BuiltInMarshaller<T> builtInMarshaller = builtInMarshaller(wrapperClass, contextless(writer), contextless(reader));
map.put(primitiveClass, builtInMarshaller);
map.put(wrapperClass, builtInMarshaller);
}
+ private static <T> ValueWriter<T> contextless(ContextlessValueWriter<T> writer) {
+ return (obj, out, ctx) -> writer.write(obj, out);
+ }
+
private static <T> ValueReader<T> contextless(ContextlessValueReader<T> reader) {
return (in, ctx) -> reader.read(in);
}
@@ -122,12 +127,13 @@ class BuiltInNonContainerMarshallers {
return builtInMarshallers.containsKey(classToCheck);
}
- Set<ClassDescriptor> writeBuiltIn(Object object, ClassDescriptor descriptor, DataOutput output) throws IOException, MarshalException {
+ void writeBuiltIn(Object object, ClassDescriptor descriptor, DataOutput output, MarshallingContext context)
+ throws IOException, MarshalException {
BuiltInMarshaller<?> builtInMarshaller = findBuiltInMarshaller(descriptor);
- builtInMarshaller.marshal(object, output);
+ builtInMarshaller.marshal(object, output, context);
- return Set.of(descriptor);
+ context.addUsedDescriptor(descriptor);
}
Object readBuiltIn(ClassDescriptor descriptor, DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
@@ -154,8 +160,8 @@ class BuiltInNonContainerMarshallers {
this.reader = reader;
}
- private void marshal(Object object, DataOutput output) throws IOException, MarshalException {
- writer.write(valueRefClass.cast(object), output);
+ private void marshal(Object object, DataOutput output, MarshallingContext context) throws IOException, MarshalException {
+ writer.write(valueRefClass.cast(object), output, context);
}
private Object unmarshal(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
@@ -163,6 +169,19 @@ class BuiltInNonContainerMarshallers {
}
}
+ interface ContextlessValueWriter<T> {
+ /**
+ * Writes the given value to a {@link DataOutput}.
+ *
+ * @param value value to write
+ * @param output where to write to
+ * @throws IOException if an I/O problem occurs
+ * @throws MarshalException if another problem occurs
+ */
+ void write(T value, DataOutput output) throws IOException, MarshalException;
+ }
+
+
private interface ContextlessValueReader<T> {
/**
* Reads the next value from a {@link DataInput}.
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
index fb72a05..146ada9 100644
--- 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
@@ -27,17 +27,17 @@ import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.Externalizable;
import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;
-import java.util.Set;
import org.apache.ignite.internal.network.serialization.BuiltinType;
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.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
import org.apache.ignite.internal.network.serialization.Null;
+import org.apache.ignite.internal.network.serialization.SerializedStreamCommands;
import org.apache.ignite.internal.network.serialization.SpecialMethodInvocationException;
import org.jetbrains.annotations.Nullable;
@@ -49,11 +49,27 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
private final ClassDescriptorFactory descriptorFactory;
private final BuiltInNonContainerMarshallers builtInNonContainerMarshallers = new BuiltInNonContainerMarshallers();
- private final BuiltInContainerMarshallers builtInContainerMarshallers = new BuiltInContainerMarshallers(this::marshalToOutput);
-
+ private final BuiltInContainerMarshallers builtInContainerMarshallers = new BuiltInContainerMarshallers(
+ (obj, out, ctx) -> marshalToOutput(obj, objectClass(obj), out, ctx)
+ );
+ private final ExternalizableMarshaller externalizableMarshaller = new ExternalizableMarshaller();
+ private final ArbitraryObjectMarshaller arbitraryObjectMarshaller;
+
+ /**
+ * Constructor.
+ *
+ * @param descriptorRegistry registry of local descriptors to consult with
+ * @param descriptorFactory descriptor factory to create new descriptors from classes
+ */
public DefaultUserObjectMarshaller(ClassDescriptorFactoryContext descriptorRegistry, ClassDescriptorFactory descriptorFactory) {
this.descriptorRegistry = descriptorRegistry;
this.descriptorFactory = descriptorFactory;
+
+ arbitraryObjectMarshaller = new ArbitraryObjectMarshaller(
+ descriptorRegistry,
+ this::marshalToOutput,
+ this::unmarshalFromInput
+ );
}
public MarshalledObject marshal(@Nullable Object object) throws MarshalException {
@@ -63,53 +79,110 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
/** {@inheritDoc} */
@Override
public MarshalledObject marshal(@Nullable Object object, Class<?> declaredClass) throws MarshalException {
- Set<ClassDescriptor> usedDescriptors;
+ MarshallingContext context = new MarshallingContext();
var baos = new ByteArrayOutputStream();
try (var dos = new DataOutputStream(baos)) {
- usedDescriptors = marshalToOutput(object, declaredClass, dos);
+ marshalToOutput(object, declaredClass, dos, context);
} catch (IOException e) {
throw new MarshalException("Cannot marshal", e);
}
- return new MarshalledObject(baos.toByteArray(), usedDescriptors);
- }
-
- private Set<ClassDescriptor> marshalToOutput(Object element, DataOutput output) throws MarshalException, IOException {
- return marshalToOutput(element, objectClass(element), output);
+ return new MarshalledObject(baos.toByteArray(), context.usedDescriptors());
}
- private Set<ClassDescriptor> marshalToOutput(@Nullable Object object, Class<?> declaredClass, DataOutput output)
+ private void marshalToOutput(@Nullable Object object, Class<?> declaredClass, DataOutput output, MarshallingContext context)
throws MarshalException, IOException {
assert declaredClass != null;
assert object == null
|| declaredClass.isPrimitive()
|| objectIsMemberOfEnumWithAnonymousClassesForMembers(object, declaredClass)
- || object.getClass() == declaredClass
+ || declaredClass.isAssignableFrom(object.getClass())
: "Object " + object + " is expected to have class " + declaredClass + ", but its " + object.getClass();
+ throwIfMarshallingNotSupported(object);
+
DescribedObject writeReplaced = applyWriteReplaceIfNeeded(object, declaredClass);
- writeDescriptorId(writeReplaced.descriptor, output);
+ if (canParticipateInCycles(writeReplaced.descriptor)) {
+ Integer maybeRefId = context.rememberAsSeen(writeReplaced.object);
+ if (maybeRefId != null) {
+ writeReference(maybeRefId, output);
+ } else {
+ marshalCycleable(writeReplaced, output, context);
+ }
+ } else {
+ marshalNonCycleable(writeReplaced, output, context);
+ }
+ }
- return writeObject(writeReplaced.object, writeReplaced.descriptor, output);
+ /**
+ * Returns {@code true} if an instance of the type represented by the descriptor may actively form a cycle.
+ *
+ * @param descriptor descriptor to check
+ * @return {@code true} if an instance of the type represented by the descriptor may actively form a cycle
+ */
+ boolean canParticipateInCycles(ClassDescriptor descriptor) {
+ return !builtInNonContainerMarshallers.supports(descriptor.clazz());
}
private boolean objectIsMemberOfEnumWithAnonymousClassesForMembers(Object object, Class<?> declaredClass) {
return declaredClass.isEnum() && object.getClass().getSuperclass() == declaredClass;
}
+ private void throwIfMarshallingNotSupported(@Nullable Object object) {
+ if (object == null) {
+ return;
+ }
+ if (Enum.class.isAssignableFrom(object.getClass())) {
+ return;
+ }
+
+ Class<?> objectClass = object.getClass();
+ if (isInnerClass(objectClass)) {
+ throw new IllegalArgumentException("Non-static inner class instances are not supported for marshalling: " + objectClass);
+ }
+ if (isCapturingClosure(objectClass)) {
+ throw new IllegalArgumentException("Capturing nested class instances are not supported for marshalling: " + object);
+ }
+ }
+
+ private boolean isInnerClass(Class<?> objectClass) {
+ return objectClass.getDeclaringClass() != null && !Modifier.isStatic(objectClass.getModifiers());
+ }
+
+ private boolean isCapturingClosure(Class<?> objectClass) {
+ for (Field field : objectClass.getDeclaredFields()) {
+ if ((field.isSynthetic() && field.getName().equals("this$0"))
+ || field.getName().startsWith("arg$")) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private DescribedObject applyWriteReplaceIfNeeded(@Nullable Object originalObject, Class<?> declaredClass) throws MarshalException {
- final ClassDescriptor originalDescriptor = getOrCreateDescriptor(declaredClass);
+ // object class is not a subclass of the declared class for primitives
+ // for enums we don't need the specific classes at all
+ Class<?> classToQueryForOriginalDescriptor = isInstanceOfSubclass(originalObject, declaredClass)
+ && !(originalObject instanceof Enum)
+ ? originalObject.getClass() : declaredClass;
+
+ final ClassDescriptor originalDescriptor = getOrCreateDescriptor(classToQueryForOriginalDescriptor);
+
+ if (originalDescriptor.supportsWriteReplace()) {
+ Object objectToWrite = applyWriteReplace(originalObject, originalDescriptor);
+ ClassDescriptor descriptorToUse = getOrCreateDescriptor(objectToWrite, objectClass(objectToWrite));
- if (!originalDescriptor.supportsWriteReplace()) {
+ return new DescribedObject(objectToWrite, descriptorToUse);
+ } else {
return new DescribedObject(originalObject, originalDescriptor);
}
+ }
- Object objectToWrite = applyWriteReplace(originalObject, originalDescriptor);
- ClassDescriptor descriptorToUse = getOrCreateDescriptor(objectToWrite, objectClass(objectToWrite));
-
- return new DescribedObject(objectToWrite, descriptorToUse);
+ private boolean isInstanceOfSubclass(@Nullable Object object, Class<?> maybeSuperclass) {
+ return object != null && maybeSuperclass.isAssignableFrom(object.getClass());
}
@Nullable
@@ -148,41 +221,67 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
ClassDescriptor descriptor = descriptorRegistry.getDescriptor(objectClass);
if (descriptor != null) {
return descriptor;
- }
+ } else {
+ // This is some custom class (not a built-in). If it's a non-built-in array, we need handle it as a generic container.
+ if (objectClass.isArray()) {
+ return descriptorRegistry.getBuiltInDescriptor(BuiltinType.OBJECT_ARRAY);
+ }
- // This is some custom class (not a built-in). If it's a non-built-in array, we need handle it as a generic container.
- if (objectClass.isArray()) {
- return descriptorRegistry.getBuiltInDescriptor(BuiltinType.OBJECT_ARRAY);
+ return descriptorFactory.create(objectClass);
}
-
- descriptor = descriptorFactory.create(objectClass);
- return descriptor;
}
private boolean isEnumArray(Class<?> objectClass) {
return objectClass.isArray() && objectClass.getComponentType().isEnum();
}
+ private void writeReference(int referenceId, DataOutput output) throws IOException {
+ writeDescriptorOrCommandId(SerializedStreamCommands.REFERENCE, output);
+ writeReferenceId(referenceId, output);
+ }
+
+ private void marshalCycleable(DescribedObject describedObject, DataOutput output, MarshallingContext context)
+ throws IOException, MarshalException {
+ writeDescriptorId(describedObject.descriptor, output);
+ writeReferenceId(context.referenceId(describedObject.object), output);
+
+ writeObject(describedObject.object, describedObject.descriptor, output, context);
+ }
+
+ private void marshalNonCycleable(DescribedObject describedObject, DataOutput output, MarshallingContext context)
+ throws IOException, MarshalException {
+ writeDescriptorId(describedObject.descriptor, output);
+
+ writeObject(describedObject.object, describedObject.descriptor, output, context);
+ }
+
private void writeDescriptorId(ClassDescriptor descriptor, DataOutput output) throws IOException {
- output.writeInt(descriptor.descriptorId());
+ writeDescriptorOrCommandId(descriptor.descriptorId(), output);
}
- private Set<ClassDescriptor> writeObject(@Nullable Object object, ClassDescriptor descriptor, DataOutput output)
+ private void writeDescriptorOrCommandId(int id, DataOutput output) throws IOException {
+ output.writeInt(id);
+ }
+
+ private void writeReferenceId(int referenceId, DataOutput output) throws IOException {
+ output.writeInt(referenceId);
+ }
+
+ private void writeObject(@Nullable Object object, ClassDescriptor descriptor, DataOutput output, MarshallingContext context)
throws IOException, MarshalException {
- if (descriptor.isNull()) {
- return Set.of(descriptor);
- } else if (isBuiltInNonContainer(descriptor)) {
- return builtInNonContainerMarshallers.writeBuiltIn(object, descriptor, output);
+ if (isBuiltInNonContainer(descriptor)) {
+ builtInNonContainerMarshallers.writeBuiltIn(object, descriptor, output, context);
} else if (isBuiltInCollection(descriptor)) {
- return builtInContainerMarshallers.writeBuiltInCollection((Collection<?>) object, descriptor, output);
+ builtInContainerMarshallers.writeBuiltInCollection((Collection<?>) object, descriptor, output, context);
} else if (isBuiltInMap(descriptor)) {
- return builtInContainerMarshallers.writeBuiltInMap((Map<?, ?>) object, descriptor, output);
+ builtInContainerMarshallers.writeBuiltInMap((Map<?, ?>) object, descriptor, output, context);
} else if (isArray(descriptor)) {
- return builtInContainerMarshallers.writeGenericRefArray((Object[]) object, descriptor, output);
+ //noinspection ConstantConditions
+ builtInContainerMarshallers.writeGenericRefArray((Object[]) object, descriptor, output, context);
} else if (descriptor.isExternalizable()) {
- return writeExternalizable((Externalizable) object, descriptor, output);
+ externalizableMarshaller.writeExternalizable((Externalizable) object, descriptor, output, context);
} else {
- throw new UnsupportedOperationException("Not supported yet");
+ arbitraryObjectMarshaller.writeArbitraryObject(object, descriptor, output, context);
}
}
@@ -202,32 +301,12 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
return descriptor.isBuiltIn() && Map.class.isAssignableFrom(descriptor.clazz());
}
- private Set<ClassDescriptor> writeExternalizable(Externalizable externalizable, ClassDescriptor descriptor, DataOutput output)
- throws IOException {
- byte[] externalizableBytes = externalize(externalizable);
-
- output.writeInt(externalizableBytes.length);
- output.write(externalizableBytes);
-
- return Set.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
@Nullable
public <T> T unmarshal(byte[] bytes, IdIndexedDescriptors mergedDescriptors) throws UnmarshalException {
- UnmarshallingContext context = new UnmarshallingContext(mergedDescriptors);
-
- try (var dis = new DataInputStream(new ByteArrayInputStream(bytes))) {
+ try (var bais = new ByteArrayInputStream(bytes); var dis = new DataInputStream(bais)) {
+ UnmarshallingContext context = new UnmarshallingContext(bais, mergedDescriptors);
return unmarshalFromInput(dis, context);
} catch (IOException e) {
throw new UnmarshalException("Cannot unmarshal", e);
@@ -235,91 +314,137 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
}
private <T> T unmarshalFromInput(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
- int descriptorId = readDescriptorId(input);
- ClassDescriptor descriptor = context.getRequiredDescriptor(descriptorId);
+ int commandOrDescriptorId = readDescriptorOrCommandId(input);
+ if (commandOrDescriptorId == SerializedStreamCommands.REFERENCE) {
+ // TODO: IGNITE-16165 - make sure readResolve() is applied correctly when we exit early due to reading a reference
+ return unmarshalReference(input, context);
+ }
+
+ ClassDescriptor descriptor = context.getRequiredDescriptor(commandOrDescriptorId);
+ Object readObject;
+ if (canParticipateInCycles(descriptor)) {
+ readObject = readCycleable(input, context, descriptor);
+ } else {
+ readObject = readObject(input, descriptor, context);
+ }
- Object readObject = readObject(input, descriptor, context);
@SuppressWarnings("unchecked") T resolvedObject = (T) applyReadResolveIfNeeded(descriptor, readObject);
return resolvedObject;
}
- private int readDescriptorId(DataInput input) throws IOException {
+ private int readDescriptorOrCommandId(DataInput input) throws IOException {
return input.readInt();
}
- private Object applyReadResolveIfNeeded(ClassDescriptor descriptor, Object object) throws UnmarshalException {
- if (descriptor.hasReadResolve()) {
- return applyReadResolve(descriptor, object);
- } else {
- return object;
- }
+ private <T> T unmarshalReference(DataInput input, UnmarshallingContext context) throws IOException {
+ int referenceId = input.readInt();
+ return context.dereference(referenceId);
}
- private Object applyReadResolve(ClassDescriptor descriptor, Object readObject) throws UnmarshalException {
- try {
- return descriptor.serializationMethods().readResolve(readObject);
- } catch (SpecialMethodInvocationException e) {
- throw new UnmarshalException("Cannot apply readResolve()", e);
- }
+ private Object readCycleable(DataInput input, UnmarshallingContext context, ClassDescriptor descriptor)
+ throws IOException, UnmarshalException {
+ int referenceId = readReferenceId(input);
+
+ Object preInstantiatedObject = preInstantiate(descriptor, input, context);
+ context.registerReference(referenceId, preInstantiatedObject);
+
+ fillObjectFrom(input, preInstantiatedObject, descriptor, context);
+
+ return preInstantiatedObject;
}
- @Nullable
- private Object readObject(DataInput input, ClassDescriptor descriptor, UnmarshallingContext context)
+ private int readReferenceId(DataInput input) throws IOException {
+ return input.readInt();
+ }
+
+ private Object preInstantiate(ClassDescriptor descriptor, DataInput input, UnmarshallingContext context)
throws IOException, UnmarshalException {
- if (descriptor.isNull()) {
- return null;
- } else if (isBuiltInNonContainer(descriptor)) {
- return builtInNonContainerMarshallers.readBuiltIn(descriptor, input, context);
+ if (isBuiltInNonContainer(descriptor)) {
+ throw new IllegalStateException("Should not be here");
} else if (isBuiltInCollection(descriptor)) {
- return readBuiltInCollection(input, descriptor, context);
+ return builtInContainerMarshallers.preInstantiateBuiltInMutableCollection(descriptor, input, context);
} else if (isBuiltInMap(descriptor)) {
- return readBuiltInMap(input, descriptor, context);
+ return builtInContainerMarshallers.preInstantiateBuiltInMutableMap(descriptor, input, context);
} else if (isArray(descriptor)) {
- return readGenericRefArray(input, context);
+ return preInstantiateGenericRefArray(input);
} else if (descriptor.isExternalizable()) {
- return readExternalizable(descriptor, input);
+ return externalizableMarshaller.preInstantiateExternalizable(descriptor);
} else {
- throw new UnsupportedOperationException("Not supported yet");
+ return arbitraryObjectMarshaller.preInstantiateArbitraryObject(descriptor);
}
}
- private Object[] readGenericRefArray(DataInput input, UnmarshallingContext context) throws IOException, UnmarshalException {
- return builtInContainerMarshallers.readGenericRefArray(input, this::unmarshalFromInput, context);
+ private Object[] preInstantiateGenericRefArray(DataInput input) throws IOException {
+ return builtInContainerMarshallers.preInstantiateGenericRefArray(input);
}
- private Collection<Object> readBuiltInCollection(DataInput input, ClassDescriptor descriptor, UnmarshallingContext context)
+ private void fillObjectFrom(DataInput input, Object preInstantiatedObject, ClassDescriptor descriptor, UnmarshallingContext context)
throws UnmarshalException, IOException {
- return builtInContainerMarshallers.readBuiltInCollection(descriptor, this::unmarshalFromInput, input, context);
+ if (isBuiltInNonContainer(descriptor)) {
+ throw new IllegalStateException("Cannot fill " + descriptor.clazz() + ", this is a programmatic error");
+ } else if (isBuiltInCollection(descriptor)) {
+ fillBuiltInCollectionFrom(input, (Collection<?>) preInstantiatedObject, descriptor, context);
+ } else if (isBuiltInMap(descriptor)) {
+ fillBuiltInMapFrom(input, (Map<?, ?>) preInstantiatedObject, context);
+ } else if (isArray(descriptor)) {
+ fillGenericRefArrayFrom(input, (Object[]) preInstantiatedObject, context);
+ } else if (descriptor.isExternalizable()) {
+ externalizableMarshaller.fillExternalizableFrom(input, (Externalizable) preInstantiatedObject);
+ } else {
+ arbitraryObjectMarshaller.fillArbitraryObjectFrom(input, preInstantiatedObject, descriptor, context);
+ }
}
- private Map<Object, Object> readBuiltInMap(DataInput input, ClassDescriptor descriptor, UnmarshallingContext context)
- throws UnmarshalException, IOException {
- return builtInContainerMarshallers.readBuiltInMap(descriptor, this::unmarshalFromInput, this::unmarshalFromInput, input, context);
+ private void fillBuiltInCollectionFrom(
+ DataInput input,
+ Collection<?> preInstantiatedObject,
+ ClassDescriptor descriptor,
+ UnmarshallingContext context
+ ) throws UnmarshalException, IOException {
+ builtInContainerMarshallers.fillBuiltInCollectionFrom(input, preInstantiatedObject, descriptor, this::unmarshalFromInput, context);
}
- private <T extends Externalizable> T readExternalizable(ClassDescriptor descriptor, DataInput input)
- throws IOException, UnmarshalException {
- T object = instantiateObject(descriptor);
+ private void fillBuiltInMapFrom(
+ DataInput input,
+ Map<?, ?> preInstantiatedObject,
+ UnmarshallingContext context
+ ) throws UnmarshalException, IOException {
+ builtInContainerMarshallers.fillBuiltInMapFrom(input,
+ preInstantiatedObject,
+ this::unmarshalFromInput,
+ this::unmarshalFromInput,
+ context
+ );
+ }
- int length = input.readInt();
- byte[] bytes = new byte[length];
- input.readFully(bytes);
+ private void fillGenericRefArrayFrom(DataInput input, Object[] array, UnmarshallingContext context)
+ throws IOException, UnmarshalException {
+ builtInContainerMarshallers.fillGenericRefArray(input, array, this::unmarshalFromInput, context);
+ }
- try (var ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
- object.readExternal(ois);
- } catch (ClassNotFoundException e) {
- throw new UnmarshalException("Cannot unmarshal due to a missing class", e);
+ @Nullable
+ private Object readObject(DataInput input, ClassDescriptor descriptor, UnmarshallingContext context)
+ throws IOException, UnmarshalException {
+ if (isBuiltInNonContainer(descriptor)) {
+ return builtInNonContainerMarshallers.readBuiltIn(descriptor, input, context);
+ } else {
+ throw new IllegalStateException("Cannot read an instance of " + descriptor.clazz() + ", this is a programmatic error");
}
+ }
- return object;
+ private Object applyReadResolveIfNeeded(ClassDescriptor descriptor, Object object) throws UnmarshalException {
+ if (descriptor.hasReadResolve()) {
+ return applyReadResolve(descriptor, object);
+ } else {
+ return object;
+ }
}
- @SuppressWarnings("unchecked")
- private <T extends Externalizable> T instantiateObject(ClassDescriptor descriptor) throws UnmarshalException {
+ private Object applyReadResolve(ClassDescriptor descriptor, Object readObject) throws UnmarshalException {
try {
- return (T) descriptor.clazz().getConstructor().newInstance();
- } catch (ReflectiveOperationException e) {
- throw new UnmarshalException("Cannot instantiate " + descriptor.clazz(), e);
+ return descriptor.serializationMethods().readResolve(readObject);
+ } catch (SpecialMethodInvocationException e) {
+ throw new UnmarshalException("Cannot apply readResolve()", e);
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ExternalizableMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ExternalizableMarshaller.java
new file mode 100644
index 0000000..6ace075
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ExternalizableMarshaller.java
@@ -0,0 +1,75 @@
+/*
+ * 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.DataInput;
+import java.io.DataOutput;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+
+/**
+ * (Um)marshalling specific to EXTERNALIZABLE serialization type.
+ */
+class ExternalizableMarshaller {
+ private final NoArgConstructorInstantiation instantiation = new NoArgConstructorInstantiation();
+
+ void writeExternalizable(Externalizable externalizable, ClassDescriptor descriptor, DataOutput output, MarshallingContext context)
+ throws IOException {
+ byte[] externalizableBytes = externalize(externalizable);
+
+ output.writeInt(externalizableBytes.length);
+ output.write(externalizableBytes);
+
+ context.addUsedDescriptor(descriptor);
+ }
+
+ private byte[] externalize(Externalizable externalizable) throws IOException {
+ var baos = new ByteArrayOutputStream();
+ try (var oos = new ObjectOutputStream(baos)) {
+ externalizable.writeExternal(oos);
+ }
+
+ return baos.toByteArray();
+ }
+
+ @SuppressWarnings("unchecked")
+ <T extends Externalizable> T preInstantiateExternalizable(ClassDescriptor descriptor) throws UnmarshalException {
+ try {
+ return (T) instantiation.newInstance(descriptor.clazz());
+ } catch (InstantiationException e) {
+ throw new UnmarshalException("Cannot instantiate " + descriptor.clazz(), e);
+ }
+ }
+
+ <T extends Externalizable> void fillExternalizableFrom(DataInput input, T object) throws IOException, UnmarshalException {
+ int length = input.readInt();
+ byte[] bytes = new byte[length];
+ input.readFully(bytes);
+
+ try (var ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
+ object.readExternal(ois);
+ } catch (ClassNotFoundException e) {
+ throw new UnmarshalException("Cannot unmarshal due to a missing class", e);
+ }
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/Instantiation.java
similarity index 50%
copy from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
copy to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/Instantiation.java
index d3d75b9..b1d39d0 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/Instantiation.java
@@ -17,20 +17,26 @@
package org.apache.ignite.internal.network.serialization.marshal;
-import java.io.DataOutput;
-import java.io.IOException;
-
/**
- * Knows how to write a value to a {@link DataOutput}.
+ * Strategy for creating an empty (not yet filled with values) instance of a class.
+ * Only used to instantiate proper classes, never gets interfaces, primitive classes and so on,
+ * so implementations should not bother checking for them in {@link #supports(Class)}.
*/
-interface ValueWriter<T> {
+interface Instantiation {
+ /**
+ * Returns {@code true} iff supports the provided class for means of instantiation.
+ *
+ * @param objectClass class to check for support
+ * @return {@code true} iff supports the provided class for means of instantiation
+ */
+ boolean supports(Class<?> objectClass);
+
/**
- * Writes the given value to a {@link DataOutput}.
+ * Creates a new instance of the provided class.
*
- * @param value value to write
- * @param output where to write to
- * @throws IOException if an I/O problem occurs
- * @throws MarshalException if another problem occurs
+ * @param objectClass class to instantiate
+ * @return new instance of the given class
+ * @throws InstantiationException if something goes wrong during instantiation
*/
- void write(T value, DataOutput output) throws IOException, MarshalException;
+ Object newInstance(Class<?> objectClass) throws InstantiationException;
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/InstantiationException.java
similarity index 64%
copy from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
copy to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/InstantiationException.java
index d3d75b9..9d600c4 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/InstantiationException.java
@@ -17,20 +17,18 @@
package org.apache.ignite.internal.network.serialization.marshal;
-import java.io.DataOutput;
-import java.io.IOException;
+import org.apache.ignite.lang.IgniteInternalCheckedException;
+import org.jetbrains.annotations.Nullable;
/**
- * Knows how to write a value to a {@link DataOutput}.
+ * Thrown if class instantiation fails.
*/
-interface ValueWriter<T> {
- /**
- * Writes the given value to a {@link DataOutput}.
- *
- * @param value value to write
- * @param output where to write to
- * @throws IOException if an I/O problem occurs
- * @throws MarshalException if another problem occurs
- */
- void write(T value, DataOutput output) throws IOException, MarshalException;
+public class InstantiationException extends IgniteInternalCheckedException {
+ public InstantiationException(String msg) {
+ super(msg);
+ }
+
+ public InstantiationException(String msg, @Nullable Throwable cause) {
+ super(msg, cause);
+ }
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java
new file mode 100644
index 0000000..5989190
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContext.java
@@ -0,0 +1,90 @@
+/*
+ * 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 java.util.Collections.unmodifiableSet;
+
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Context using during marshalling of an object graph accessible from a root object.
+ */
+class MarshallingContext {
+ private final Set<ClassDescriptor> usedDescriptors = new HashSet<>();
+
+ private final Map<Object, Integer> objectsToRefIds = new IdentityHashMap<>();
+
+ private int nextRefId = 0;
+
+ public void addUsedDescriptor(ClassDescriptor descriptor) {
+ usedDescriptors.add(descriptor);
+ }
+
+ public Set<ClassDescriptor> usedDescriptors() {
+ return unmodifiableSet(usedDescriptors);
+ }
+
+ /**
+ * If the object was already seen before, its ID is returned; otherwise, it's memorized as seen with a fresh ID.
+ *
+ * @param object object to operate upon
+ * @return object ID if it was seen earlier or {@code null} if the object is new
+ */
+ @Nullable
+ public Integer rememberAsSeen(@Nullable Object object) {
+ if (object == null) {
+ return null;
+ }
+
+ Integer prevRefId = objectsToRefIds.get(object);
+ if (prevRefId != null) {
+ return prevRefId;
+ } else {
+ int newRefId = nextRefId();
+
+ objectsToRefIds.put(object, newRefId);
+
+ return null;
+ }
+ }
+
+ private int nextRefId() {
+ return nextRefId++;
+ }
+
+ /**
+ * Returns a reference ID by the given object.
+ *
+ * @param object lookup object
+ * @return object ID
+ */
+ public int referenceId(Object object) {
+ Integer refId = objectsToRefIds.get(object);
+
+ if (refId == null) {
+ throw new IllegalStateException("No reference created yet for " + object);
+ }
+
+ return refId;
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/NoArgConstructorInstantiation.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/NoArgConstructorInstantiation.java
new file mode 100644
index 0000000..acfaf56
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/NoArgConstructorInstantiation.java
@@ -0,0 +1,50 @@
+/*
+ * 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.reflect.Constructor;
+
+/**
+ * Instantiates using no-arg constructor. It only supports classes that have such constructors (but the constructors
+ * may be private, they are still supported).
+ */
+class NoArgConstructorInstantiation implements Instantiation {
+ /** {@inheritDoc} */
+ @Override
+ public boolean supports(Class<?> objectClass) {
+ for (Constructor<?> constructor : objectClass.getDeclaredConstructors()) {
+ if (constructor.getParameterCount() == 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object newInstance(Class<?> objectClass) throws InstantiationException {
+ try {
+ Constructor<?> constructor = objectClass.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ return constructor.newInstance();
+ } catch (ReflectiveOperationException e) {
+ throw new InstantiationException("Cannot instantiate " + objectClass, e);
+ }
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiation.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiation.java
new file mode 100644
index 0000000..665bfb1
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiation.java
@@ -0,0 +1,115 @@
+/*
+ * 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.DataOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.io.ObjectStreamConstants;
+import java.io.Serializable;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.apache.ignite.internal.network.serialization.ClassIndexedDescriptors;
+
+/**
+ * Instantiates {@link Serializable} classes (they are the only ones supported) by crafting a representation of
+ * a serialized object of a given class (without any field data) and then deserializing it using the standard
+ * Java Serialization.
+ */
+class SerializableInstantiation implements Instantiation {
+
+ private static final int STREAM_VERSION = 5;
+
+ private final ClassIndexedDescriptors descriptors;
+
+ SerializableInstantiation(ClassIndexedDescriptors descriptors) {
+ this.descriptors = descriptors;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean supports(Class<?> objectClass) {
+ if (!Serializable.class.isAssignableFrom(objectClass)) {
+ return false;
+ }
+
+ ClassDescriptor descriptor = descriptors.getRequiredDescriptor(objectClass);
+ return !descriptor.hasWriteReplace() && !descriptor.hasReadResolve();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object newInstance(Class<?> objectClass) throws InstantiationException {
+ byte[] jdkSerialization = jdkSerializationOfEmptyInstanceOf(objectClass);
+
+ try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(jdkSerialization))) {
+ return ois.readObject();
+ } catch (IOException | ClassNotFoundException e) {
+ throw new InstantiationException("Cannot deserialize JDK serialization of an empty instance", e);
+ }
+ }
+
+ private byte[] jdkSerializationOfEmptyInstanceOf(Class<?> objectClass) throws InstantiationException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ try (DataOutputStream dos = new DataOutputStream(baos)) {
+ writeSignature(dos);
+
+ dos.writeByte(ObjectStreamConstants.TC_OBJECT);
+ dos.writeByte(ObjectStreamConstants.TC_CLASSDESC);
+ dos.writeUTF(objectClass.getName());
+
+ dos.writeLong(serialVersionUid(objectClass));
+
+ writeFlags(dos);
+
+ writeZeroFields(dos);
+
+ dos.writeByte(ObjectStreamConstants.TC_ENDBLOCKDATA);
+ writeNullForNoParentDescriptor(dos);
+ } catch (IOException e) {
+ throw new InstantiationException("Cannot create JDK serialization of an empty instance", e);
+ }
+
+ return baos.toByteArray();
+ }
+
+ private void writeSignature(DataOutputStream dos) throws IOException {
+ dos.writeShort(ObjectStreamConstants.STREAM_MAGIC);
+ dos.writeShort(STREAM_VERSION);
+ }
+
+ private long serialVersionUid(Class<?> objectClass) {
+ ObjectStreamClass descriptor = ObjectStreamClass.lookup(objectClass);
+ return descriptor.getSerialVersionUID();
+ }
+
+ private void writeFlags(DataOutputStream dos) throws IOException {
+ dos.writeByte(ObjectStreamConstants.SC_SERIALIZABLE);
+ }
+
+ private void writeZeroFields(DataOutputStream dos) throws IOException {
+ dos.writeShort(0);
+ }
+
+ private void writeNullForNoParentDescriptor(DataOutputStream dos) throws IOException {
+ dos.writeByte(ObjectStreamConstants.TC_NULL);
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/TrackingMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/TypedValueWriter.java
similarity index 64%
rename from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/TrackingMarshaller.java
rename to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/TypedValueWriter.java
index 718a34d..294bc4e 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/TrackingMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/TypedValueWriter.java
@@ -19,21 +19,21 @@ package org.apache.ignite.internal.network.serialization.marshal;
import java.io.DataOutput;
import java.io.IOException;
-import java.util.Set;
-import org.apache.ignite.internal.network.serialization.ClassDescriptor;
/**
- * Marshals objects to a {@link DataOutput} and also tracks what {@link ClassDescriptor}s were used when marshalling.
+ * Writes objects to a {@link DataOutput} taking their original (for example, declared) types into consideration.
*/
-interface TrackingMarshaller {
+interface TypedValueWriter {
/**
- * Marshals the given object to the {@link DataOutput}.
+ * Writes the given object to the {@link DataOutput}.
*
- * @param object object to marshal
- * @param output where to marshal to
- * @return {@link ClassDescriptor}s that were used when marshalling
+ * @param object object to write
+ * @param declaredClass the original class of the object (i.e. {@code byte.class} for {@code byte})
+ * @param output where to write to
+ * @param context marshalling context
* @throws IOException if an I/O problem occurs
* @throws MarshalException if another problem occurs
*/
- Set<ClassDescriptor> marshal(Object object, DataOutput output) throws IOException, MarshalException;
+ void write(Object object, Class<?> declaredClass, DataOutput output, MarshallingContext context)
+ throws IOException, MarshalException;
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
index ebea301..94d7325 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
@@ -17,6 +17,9 @@
package org.apache.ignite.internal.network.serialization.marshal;
+import java.io.ByteArrayInputStream;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.ignite.internal.network.serialization.ClassDescriptor;
import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
import org.jetbrains.annotations.Nullable;
@@ -25,9 +28,13 @@ import org.jetbrains.annotations.Nullable;
* Context of unmarshalling act. Created once per unmarshalling a root object.
*/
class UnmarshallingContext implements IdIndexedDescriptors {
+ private final ByteArrayInputStream source;
private final IdIndexedDescriptors descriptors;
- public UnmarshallingContext(IdIndexedDescriptors descriptors) {
+ private final Map<Integer, Object> refsToObjects = new HashMap<>();
+
+ public UnmarshallingContext(ByteArrayInputStream source, IdIndexedDescriptors descriptors) {
+ this.source = source;
this.descriptors = descriptors;
}
@@ -36,4 +43,27 @@ class UnmarshallingContext implements IdIndexedDescriptors {
public @Nullable ClassDescriptor getDescriptor(int descriptorId) {
return descriptors.getDescriptor(descriptorId);
}
+
+ public void registerReference(int referenceId, Object object) {
+ refsToObjects.put(referenceId, object);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T dereference(int referenceId) {
+ Object result = refsToObjects.get(referenceId);
+
+ if (result == null) {
+ throw new IllegalStateException("Unknown reference: " + referenceId);
+ }
+
+ return (T) result;
+ }
+
+ public void markSource() {
+ source.mark(4);
+ }
+
+ public void resetSourceToMark() {
+ source.reset();
+ }
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnsafeInstantiation.java
similarity index 58%
copy from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
copy to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnsafeInstantiation.java
index ebea301..5912344 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnmarshallingContext.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UnsafeInstantiation.java
@@ -17,23 +17,26 @@
package org.apache.ignite.internal.network.serialization.marshal;
-import org.apache.ignite.internal.network.serialization.ClassDescriptor;
-import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
-import org.jetbrains.annotations.Nullable;
+import org.apache.ignite.internal.util.GridUnsafe;
/**
- * Context of unmarshalling act. Created once per unmarshalling a root object.
+ * Instantiation strategy that uses {@link sun.misc.Unsafe#allocateInstance(Class)} to create an empty instance.
+ * It supports any class.
*/
-class UnmarshallingContext implements IdIndexedDescriptors {
- private final IdIndexedDescriptors descriptors;
-
- public UnmarshallingContext(IdIndexedDescriptors descriptors) {
- this.descriptors = descriptors;
+class UnsafeInstantiation implements Instantiation {
+ /** {@inheritDoc} */
+ @Override
+ public boolean supports(Class<?> objectClass) {
+ return true;
}
/** {@inheritDoc} */
@Override
- public @Nullable ClassDescriptor getDescriptor(int descriptorId) {
- return descriptors.getDescriptor(descriptorId);
+ public Object newInstance(Class<?> objectClass) throws InstantiationException {
+ try {
+ return GridUnsafe.allocateInstance(objectClass);
+ } catch (java.lang.InstantiationException e) {
+ throw new InstantiationException("Cannot instantiate " + objectClass, e);
+ }
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
index d3d75b9..fe18d79 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
@@ -27,10 +27,11 @@ interface ValueWriter<T> {
/**
* Writes the given value to a {@link DataOutput}.
*
- * @param value value to write
- * @param output where to write to
+ * @param value value to write
+ * @param output where to write to
+ * @param context marshalling context
* @throws IOException if an I/O problem occurs
* @throws MarshalException if another problem occurs
*/
- void write(T value, DataOutput output) throws IOException, MarshalException;
+ void write(T value, DataOutput output, MarshallingContext context) throws IOException, MarshalException;
}
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 ca5ab1c..332097e 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,6 +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.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -441,4 +443,33 @@ public class ClassDescriptorFactoryTest {
assertEquals(writeReplace, serialization.hasWriteReplace());
assertEquals(readResolve, serialization.hasReadResolve());
}
+
+ @Test
+ void shouldSortArbitraryObjectFieldsByClassHierarchyAndLexicographicallyByFieldName() {
+ ClassDescriptor descriptor = factory.create(ArbitraryWithFieldNameClashAndOrderPermutation.class);
+
+ assertThat(descriptor.fields().get(0).clazz(), is(String.class));
+ assertThat(descriptor.fields().get(0).name(), is("value"));
+
+ assertThat(descriptor.fields().get(1).clazz(), is(int.class));
+ assertThat(descriptor.fields().get(1).name(), is("apple"));
+
+ assertThat(descriptor.fields().get(2).clazz(), is(int.class));
+ assertThat(descriptor.fields().get(2).name(), is("banana"));
+
+ assertThat(descriptor.fields().get(3).clazz(), is(int.class));
+ assertThat(descriptor.fields().get(3).name(), is("value"));
+ }
+
+ @SuppressWarnings("unused")
+ private static class Parent {
+ private String value;
+ }
+
+ @SuppressWarnings("unused")
+ private static class ArbitraryWithFieldNameClashAndOrderPermutation extends Parent {
+ private int value;
+ private int banana;
+ private int apple;
+ }
}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/BestEffortInstantiationTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/BestEffortInstantiationTest.java
new file mode 100644
index 0000000..4dccead
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/BestEffortInstantiationTest.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.marshal;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.when;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class BestEffortInstantiationTest {
+ @Mock
+ private Instantiation delegate1;
+ @Mock
+ private Instantiation delegate2;
+
+ private BestEffortInstantiation instantiation;
+
+ @BeforeEach
+ void initMocks() throws Exception {
+ lenient().when(delegate1.newInstance(any())).thenReturn("first");
+ lenient().when(delegate2.newInstance(any())).thenReturn("second");
+ }
+
+ @BeforeEach
+ void createObjectUnderTest() {
+ instantiation = new BestEffortInstantiation(delegate1, delegate2);
+ }
+
+ @Test
+ void whenFirstDelegateSupportsThenThisSupports() {
+ when(delegate1.supports(any())).thenReturn(true);
+
+ assertTrue(instantiation.supports(Object.class));
+ }
+
+ @Test
+ void whenOnlySecondDelegateSupportsThenThisSupports() {
+ when(delegate2.supports(any())).thenReturn(true);
+
+ assertTrue(instantiation.supports(Object.class));
+ }
+
+ @Test
+ void whenNoDelegateSupportsThenThisDoesNotSupport() {
+ assertFalse(instantiation.supports(Object.class));
+ }
+
+ @Test
+ void whenFirstDelegateSupportsThenItIsUsedForInstantiation() throws Exception {
+ when(delegate1.supports(any())).thenReturn(true);
+
+ Object instance = instantiation.newInstance(Object.class);
+
+ assertThat(instance, is("first"));
+ }
+
+ @Test
+ void whenOnlySecondDelegateSupportsThenItIsUsedForInstantiation() throws Exception {
+ when(delegate2.supports(any())).thenReturn(true);
+
+ Object instance = instantiation.newInstance(Object.class);
+
+ assertThat(instance, is("second"));
+ }
+
+ @Test
+ void whenNoDelegateSupportsThenInstantiationFails() {
+ InstantiationException ex = assertThrows(InstantiationException.class, () -> instantiation.newInstance(Object.class));
+ assertThat(ex.getMessage(), is("No delegate supports " + Object.class));
+ }
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
new file mode 100644
index 0000000..51201ab
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
@@ -0,0 +1,504 @@
+/*
+ * 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 java.util.Collections.singletonList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import org.apache.ignite.internal.network.serialization.BuiltinType;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
+import org.apache.ignite.internal.network.serialization.ClassDescriptorFactoryContext;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for how {@link DefaultUserObjectMarshaller} handles arbitrary objects.
+ */
+class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
+ private final ClassDescriptorFactoryContext descriptorRegistry = new ClassDescriptorFactoryContext();
+ private final ClassDescriptorFactory descriptorFactory = new ClassDescriptorFactory(descriptorRegistry);
+
+ private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
+
+ @Test
+ void marshalsAndUnmarshalsSimpleClassInstances() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new Simple(42));
+
+ Simple unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.value, is(42));
+ }
+
+ private <T> T unmarshalNonNull(MarshalledObject marshalled) throws UnmarshalException {
+ T unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
+
+ assertThat(unmarshalled, is(notNullValue()));
+
+ return unmarshalled;
+ }
+
+ @Test
+ void marshalsArbitraryObjectsUsingDescriptorsOfThemAndTheirContents() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new Simple(42));
+
+ assertThat(marshalled.usedDescriptors(), equalTo(Set.of(
+ descriptorRegistry.getRequiredDescriptor(Simple.class),
+ descriptorRegistry.getBuiltInDescriptor(BuiltinType.INT)
+ )));
+ }
+
+ @Test
+ void marshalsArbitraryObjectWithCorrectDescriptorIdInMarshalledRepresentation() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new Simple(42));
+
+ assertThat(readType(marshalled), is(descriptorRegistry.getRequiredDescriptor(Simple.class).descriptorId()));
+ }
+
+ private int readType(MarshalledObject marshalled) throws IOException {
+ try (var dis = new DataInputStream(new ByteArrayInputStream(marshalled.bytes()))) {
+ return dis.readInt();
+ }
+ }
+
+ @Test
+ void marshalsAndUnmarshalsClassInstancesInvolvingSuperclasses() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new SimpleChild("answer", 42));
+
+ SimpleChild unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.parentValue(), is("answer"));
+ assertThat(unmarshalled.childValue(), is(42));
+ }
+
+ @Test
+ void marshalsAndUnmarshalsClassInstancesHavingNestedArbitraryObjects() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new WithArbitraryClassField(new Simple(42)));
+
+ WithArbitraryClassField unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.nested, is(notNullValue()));
+ assertThat(unmarshalled.nested.value, is(42));
+ }
+
+ @Test
+ void marshalsAndUnmarshalsClassInstancesHavingCollectionsOfArbitraryObjects() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(withArbitraryObjectInArrayList(new Simple(42)));
+
+ WithArbitraryObjectInList unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.list, hasSize(1));
+ assertThat(unmarshalled.list.get(0).value, is(42));
+ }
+
+ private WithArbitraryObjectInList withArbitraryObjectInArrayList(Simple object) {
+ List<Simple> list = new ArrayList<>(List.of(object));
+ return new WithArbitraryObjectInList(list);
+ }
+
+ @Test
+ void marshalsAndUnmarshalsClassInstancesHavingPolymorphicNestedArbitraryObjects() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new WithArbitraryClassField(new ChildOfSimple(42)));
+
+ WithArbitraryClassField unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.nested, is(instanceOf(ChildOfSimple.class)));
+ assertThat(unmarshalled.nested.value, is(42));
+ }
+
+ @Test
+ void marshalsAndUnmarshalsClassInstancesHavingCollectionsOfPolymorphicArbitraryObjects() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(withArbitraryObjectInArrayList(new ChildOfSimple(42)));
+
+ WithArbitraryObjectInList unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.list, hasSize(1));
+ assertThat(unmarshalled.list.get(0), is(instanceOf(ChildOfSimple.class)));
+ assertThat(unmarshalled.list.get(0).value, is(42));
+ }
+
+ @Test
+ void restoresConcreteCollectionTypeCorrectlyWhenUnmarshalls() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(withArbitraryObjectInArrayList(new Simple(42)));
+
+ WithArbitraryObjectInList unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.list, is(instanceOf(ArrayList.class)));
+ }
+
+ @Test
+ void ignoresTransientFields() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new WithTransientFields("Hi"));
+
+ WithTransientFields unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.value, is(nullValue()));
+ }
+
+ @Test
+ void supportsFinalFields() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new WithFinalFields(42));
+
+ WithFinalFields unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.value, is(42));
+ }
+
+ @Test
+ void supportsNonCapturingAnonymousClassInstances() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(nonCapturingAnonymousInstance());
+
+ Callable<String> unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.call(), is("Hi!"));
+ }
+
+ @SuppressWarnings("Convert2Lambda")
+ private static Callable<String> nonCapturingAnonymousInstance() {
+ return new Callable<>() {
+ @Override
+ public String call() {
+ return "Hi!";
+ }
+ };
+ }
+
+ @Test
+ void supportsNonCapturingLambdas() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(nonCapturingLambda());
+
+ Callable<String> unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.call(), is("Hi!"));
+ }
+
+ private static Callable<String> nonCapturingLambda() {
+ return () -> "Hi!";
+ }
+
+ @Test
+ @Disabled("IGNITE-16165")
+ // TODO: IGNITE-16165 - enable this test when we are able to work with serializable lambdas
+ void supportsNonCapturingSerializableLambdas() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(nonCapturingSerializableLambda());
+
+ Callable<String> unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.call(), is("Hi!"));
+ }
+
+ private static Callable<String> nonCapturingSerializableLambda() {
+ return (Callable<String> & Serializable) () -> "Hi!";
+ }
+
+ @Test
+ void doesNotSupportInnerClassInstances() {
+ Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(new Inner()));
+ assertThat(ex.getMessage(), is("Non-static inner class instances are not supported for marshalling: " + Inner.class));
+ }
+
+ @Test
+ void doesNotSupportInnerClassInstancesInsideContainers() {
+ List<Inner> list = singletonList(new Inner());
+
+ Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(list));
+ assertThat(ex.getMessage(), is("Non-static inner class instances are not supported for marshalling: " + Inner.class));
+ }
+
+ @Test
+ void doesNotSupportCapturingAnonymousClassInstances() {
+ Runnable capturingClosure = capturingAnonymousInstance();
+
+ Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(capturingClosure));
+ assertThat(ex.getMessage(), startsWith("Capturing nested class instances are not supported for marshalling: "));
+ }
+
+ private Runnable capturingAnonymousInstance() {
+ //noinspection Convert2Lambda
+ return new Runnable() {
+ @Override
+ public void run() {
+ System.out.println(DefaultUserObjectMarshallerWithArbitraryObjectsTest.this);
+ }
+ };
+ }
+
+ @Test
+ void doesNotSupportCapturingAnonymousClassInstancesInsideContainers() {
+ Runnable capturingAnonymousInstance = capturingAnonymousInstance();
+ List<Runnable> list = singletonList(capturingAnonymousInstance);
+
+ Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(list));
+ assertThat(ex.getMessage(), startsWith("Capturing nested class instances are not supported for marshalling: "));
+ }
+
+ @Test
+ void doesNotSupportCapturingLambdas() {
+ Runnable capturingClosure = capturingLambda();
+
+ Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(capturingClosure));
+ assertThat(ex.getMessage(), startsWith("Capturing nested class instances are not supported for marshalling: "));
+ }
+
+ private Runnable capturingLambda() {
+ return () -> System.out.println(DefaultUserObjectMarshallerWithArbitraryObjectsTest.this);
+ }
+
+ @Test
+ void doesNotSupportCapturingAnonymousLambdasInsideContainers() {
+ Runnable capturingLambda = capturingLambda();
+ List<Runnable> list = singletonList(capturingLambda);
+
+ Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(list));
+ assertThat(ex.getMessage(), startsWith("Capturing nested class instances are not supported for marshalling: "));
+ }
+
+ @Test
+ void supportsNonCapturingLocalClassInstances() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(nonCapturingLocalClassInstance());
+
+ Callable<String> unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.call(), is("Hi!"));
+ }
+
+ private static Object nonCapturingLocalClassInstance() {
+ class Local implements Callable<String> {
+ /** {@inheritDoc} */
+ @Override
+ public String call() {
+ return "Hi!";
+ }
+ }
+
+ return new Local();
+ }
+
+ @Test
+ void doesNotSupportCapturingLocalClassInstances() {
+ Object instance = capturingLocalClassInstance();
+
+ Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(instance));
+ assertThat(ex.getMessage(), startsWith("Capturing nested class instances are not supported for marshalling: "));
+ }
+
+ private Object capturingLocalClassInstance() {
+ class Local {
+ }
+
+ return new Local();
+ }
+
+ @Test
+ void supportsClassesWithoutNoArgConstructor() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new WithoutNoArgConstructor(42));
+
+ WithoutNoArgConstructor unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.value, is(42));
+ }
+
+ @Test
+ void supportsInstancesDirectlyContainingThemselvesInFields() throws Exception {
+ MarshalledObject marshalled = marshaller.marshal(new WithInfiniteCycleViaField(42));
+
+ WithInfiniteCycleViaField unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.value, is(42));
+ assertThat(unmarshalled.myself, is(sameInstance(unmarshalled)));
+ }
+
+ @Test
+ void supportsInstancesParticipatingInIndirectInfiniteCyclesViaArbitraryObjects() throws Exception {
+ WithFirstCyclePart first = new WithFirstCyclePart();
+ WithSecondCyclePart second = new WithSecondCyclePart();
+ first.part = second;
+ second.part = first;
+
+ MarshalledObject marshalled = marshaller.marshal(first);
+
+ WithFirstCyclePart unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.part.part, is(sameInstance(unmarshalled)));
+ }
+
+ @Test
+ void supportsInstancesParticipatingInIndirectInfiniteCyclesViaMutableContainers() throws Exception {
+ WithObjectList object = new WithObjectList();
+ List<Object> container = new ArrayList<>();
+ object.contents = container;
+ container.add(object);
+
+ MarshalledObject marshalled = marshaller.marshal(object);
+
+ WithObjectList unmarshalled = unmarshalNonNull(marshalled);
+
+ assertThat(unmarshalled.contents.get(0), is(sameInstance(unmarshalled)));
+ }
+
+ private static class Simple {
+ private int value;
+
+ @SuppressWarnings("unused") // needed for instantiation
+ public Simple() {
+ }
+
+ public Simple(int value) {
+ this.value = value;
+ }
+ }
+
+ private abstract static class Parent {
+ private String value;
+
+ public Parent() {
+ }
+
+ public Parent(String value) {
+ this.value = value;
+ }
+
+ String parentValue() {
+ return value;
+ }
+ }
+
+ private static class SimpleChild extends Parent {
+ private int value;
+
+ @SuppressWarnings("unused") // needed for instantiation
+ public SimpleChild() {
+ }
+
+ public SimpleChild(String parentValue, int childValue) {
+ super(parentValue);
+ this.value = childValue;
+ }
+
+ int childValue() {
+ return value;
+ }
+ }
+
+ private static class WithArbitraryClassField {
+ private Simple nested;
+
+ @SuppressWarnings("unused") // used for instantiation
+ public WithArbitraryClassField() {
+ }
+
+ public WithArbitraryClassField(Simple nested) {
+ this.nested = nested;
+ }
+ }
+
+ private static class WithArbitraryObjectInList {
+ private List<Simple> list;
+
+ @SuppressWarnings("unused") // needed for instantiation
+ public WithArbitraryObjectInList() {
+ }
+
+ public WithArbitraryObjectInList(List<Simple> list) {
+ this.list = list;
+ }
+ }
+
+ private static class ChildOfSimple extends Simple {
+ @SuppressWarnings("unused") // needed for instantiation
+ public ChildOfSimple() {
+ }
+
+ public ChildOfSimple(int value) {
+ super(value);
+ }
+ }
+
+ private static class WithTransientFields {
+ private transient String value;
+
+ @SuppressWarnings("unused") // needed for instantiation
+ public WithTransientFields() {
+ }
+
+ public WithTransientFields(String value) {
+ this.value = value;
+ }
+ }
+
+ private static class WithFinalFields {
+ private final int value;
+
+ @SuppressWarnings("unused") // needed for instantiation
+ public WithFinalFields() {
+ this(0);
+ }
+
+ private WithFinalFields(int value) {
+ this.value = value;
+ }
+ }
+
+ @SuppressWarnings("InnerClassMayBeStatic")
+ private class Inner {
+ }
+
+ private static class WithInfiniteCycleViaField {
+ private int value;
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private WithInfiniteCycleViaField myself;
+
+ @SuppressWarnings("unused")
+ public WithInfiniteCycleViaField() {
+ }
+
+ public WithInfiniteCycleViaField(int value) {
+ this.value = value;
+
+ this.myself = this;
+ }
+ }
+
+ private static class WithFirstCyclePart {
+ private WithSecondCyclePart part;
+ }
+
+ private static class WithSecondCyclePart {
+ private WithFirstCyclePart part;
+ }
+
+ private static class WithObjectList {
+ private List<Object> contents;
+ }
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
index e8aafcc..937986f 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
@@ -23,7 +23,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assumptions.assumingThat;
import java.io.ByteArrayInputStream;
@@ -42,13 +42,16 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
import java.util.stream.Stream;
import org.apache.ignite.internal.network.serialization.BuiltinType;
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.apache.ignite.internal.network.serialization.Null;
import org.apache.ignite.lang.IgniteUuid;
-import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -64,35 +67,6 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
@Test
- void marshalsAndUnmarshalsNull() throws Exception {
- MarshalledObject marshalled = marshaller.marshal(null);
-
- Object unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
-
- assertThat(unmarshalled, is(nullValue()));
- }
-
- @Test
- void marshalsNullUsingOnlyNullDescriptor() throws Exception {
- MarshalledObject marshalled = marshaller.marshal(null);
-
- assertThat(marshalled.usedDescriptors(), equalTo(Set.of(descriptorRegistry.getNullDescriptor())));
- }
-
- @Test
- void marshalsNullWithCorrectDescriptorIdInMarshalledRepresentation() throws Exception {
- MarshalledObject marshalled = marshaller.marshal(null);
-
- assertThat(readType(marshalled), is(BuiltinType.NULL.descriptorId()));
- }
-
- private int readType(MarshalledObject marshalled) throws IOException {
- try (var dis = new DataInputStream(new ByteArrayInputStream(marshalled.bytes()))) {
- return dis.readInt();
- }
- }
-
- @Test
void marshalsAndUnmarshalsBareObject() throws Exception {
MarshalledObject marshalled = marshaller.marshal(new Object());
@@ -123,6 +97,12 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
assertThat(readType(marshalled), is(BuiltinType.BARE_OBJECT.descriptorId()));
}
+ private int readType(MarshalledObject marshalled) throws IOException {
+ try (var dis = new DataInputStream(new ByteArrayInputStream(marshalled.bytes()))) {
+ return dis.readInt();
+ }
+ }
+
@Test
void marshalsObjectArrayUsingExactlyDescriptorsOfObjectArrayAndComponents() throws Exception {
MarshalledObject marshalled = marshaller.marshal(new Object[]{42, "abc"});
@@ -149,7 +129,8 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
Object unmarshalled = marshaller.unmarshal(marshalled.bytes(), descriptorRegistry);
assertThat(unmarshalled, is(equalTo(typeValue.value)));
- if (typeValue.builtinType != BuiltinType.VOID && typeValue.value.getClass().isArray()) {
+ if (typeValue.builtinType != BuiltinType.VOID && typeValue.builtinType != BuiltinType.NULL
+ && typeValue.value.getClass().isArray()) {
assertThat(unmarshalled, is(notNullValue()));
assertThat(unmarshalled.getClass().getComponentType(), is(typeValue.value.getClass().getComponentType()));
}
@@ -170,57 +151,58 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
static Stream<Arguments> builtInNonCollectionTypes() {
return Stream.of(
- builtInTypeValueArg((byte) 42, byte.class, BuiltinType.BYTE),
- builtInTypeValueArg((byte) 42, Byte.class, BuiltinType.BYTE_BOXED),
- builtInTypeValueArg((short) 42, short.class, BuiltinType.SHORT),
- builtInTypeValueArg((short) 42, Short.class, BuiltinType.SHORT_BOXED),
- builtInTypeValueArg(42, int.class, BuiltinType.INT),
- builtInTypeValueArg(42, Integer.class, BuiltinType.INT_BOXED),
- builtInTypeValueArg(42.0f, float.class, BuiltinType.FLOAT),
- builtInTypeValueArg(42.0f, Float.class, BuiltinType.FLOAT_BOXED),
- builtInTypeValueArg((long) 42, long.class, BuiltinType.LONG),
- builtInTypeValueArg((long) 42, Long.class, BuiltinType.LONG_BOXED),
- builtInTypeValueArg(42.0, double.class, BuiltinType.DOUBLE),
- builtInTypeValueArg(42.0, Double.class, BuiltinType.DOUBLE_BOXED),
- builtInTypeValueArg(true, boolean.class, BuiltinType.BOOLEAN),
- builtInTypeValueArg(true, Boolean.class, BuiltinType.BOOLEAN_BOXED),
- builtInTypeValueArg('a', char.class, BuiltinType.CHAR),
- builtInTypeValueArg('a', Character.class, BuiltinType.CHAR_BOXED),
+ builtInTypeValue((byte) 42, byte.class, BuiltinType.BYTE),
+ builtInTypeValue((byte) 42, Byte.class, BuiltinType.BYTE_BOXED),
+ builtInTypeValue((short) 42, short.class, BuiltinType.SHORT),
+ builtInTypeValue((short) 42, Short.class, BuiltinType.SHORT_BOXED),
+ builtInTypeValue(42, int.class, BuiltinType.INT),
+ builtInTypeValue(42, Integer.class, BuiltinType.INT_BOXED),
+ builtInTypeValue(42.0f, float.class, BuiltinType.FLOAT),
+ builtInTypeValue(42.0f, Float.class, BuiltinType.FLOAT_BOXED),
+ builtInTypeValue((long) 42, long.class, BuiltinType.LONG),
+ builtInTypeValue((long) 42, Long.class, BuiltinType.LONG_BOXED),
+ builtInTypeValue(42.0, double.class, BuiltinType.DOUBLE),
+ builtInTypeValue(42.0, Double.class, BuiltinType.DOUBLE_BOXED),
+ builtInTypeValue(true, boolean.class, BuiltinType.BOOLEAN),
+ builtInTypeValue(true, Boolean.class, BuiltinType.BOOLEAN_BOXED),
+ builtInTypeValue('a', char.class, BuiltinType.CHAR),
+ builtInTypeValue('a', Character.class, BuiltinType.CHAR_BOXED),
// BARE_OBJECT is handled separately
- builtInTypeValueArg("abc", String.class, BuiltinType.STRING),
- builtInTypeValueArg(UUID.fromString("c6f57d4a-619f-11ec-add6-73bc97c3c49e"), UUID.class, BuiltinType.UUID),
- builtInTypeValueArg(IgniteUuid.fromString("1234-c6f57d4a-619f-11ec-add6-73bc97c3c49e"), IgniteUuid.class,
+ builtInTypeValue("abc", String.class, BuiltinType.STRING),
+ builtInTypeValue(UUID.fromString("c6f57d4a-619f-11ec-add6-73bc97c3c49e"), UUID.class, BuiltinType.UUID),
+ builtInTypeValue(IgniteUuid.fromString("1234-c6f57d4a-619f-11ec-add6-73bc97c3c49e"), IgniteUuid.class,
BuiltinType.IGNITE_UUID),
- builtInTypeValueArg(new Date(42), Date.class, BuiltinType.DATE),
- builtInTypeValueArg(new byte[]{1, 2, 3}, byte[].class, BuiltinType.BYTE_ARRAY),
- builtInTypeValueArg(new short[]{1, 2, 3}, short[].class, BuiltinType.SHORT_ARRAY),
- builtInTypeValueArg(new int[]{1, 2, 3}, int[].class, BuiltinType.INT_ARRAY),
- builtInTypeValueArg(new float[]{1.0f, 2.0f, 3.0f}, float[].class, BuiltinType.FLOAT_ARRAY),
- builtInTypeValueArg(new long[]{1, 2, 3}, long[].class, BuiltinType.LONG_ARRAY),
- builtInTypeValueArg(new double[]{1.0, 2.0, 3.0}, double[].class, BuiltinType.DOUBLE_ARRAY),
- builtInTypeValueArg(new boolean[]{true, false}, boolean[].class, BuiltinType.BOOLEAN_ARRAY),
- builtInTypeValueArg(new char[]{'a', 'b'}, char[].class, BuiltinType.CHAR_ARRAY),
- builtInTypeValueArg(new Object[]{42, "123", null}, Object[].class, BuiltinType.OBJECT_ARRAY),
- builtInTypeValueArg(new BitSet[]{BitSet.valueOf(new long[]{42, 43}), BitSet.valueOf(new long[]{1, 2}), null},
+ builtInTypeValue(new Date(42), Date.class, BuiltinType.DATE),
+ builtInTypeValue(new byte[]{1, 2, 3}, byte[].class, BuiltinType.BYTE_ARRAY),
+ builtInTypeValue(new short[]{1, 2, 3}, short[].class, BuiltinType.SHORT_ARRAY),
+ builtInTypeValue(new int[]{1, 2, 3}, int[].class, BuiltinType.INT_ARRAY),
+ builtInTypeValue(new float[]{1.0f, 2.0f, 3.0f}, float[].class, BuiltinType.FLOAT_ARRAY),
+ builtInTypeValue(new long[]{1, 2, 3}, long[].class, BuiltinType.LONG_ARRAY),
+ builtInTypeValue(new double[]{1.0, 2.0, 3.0}, double[].class, BuiltinType.DOUBLE_ARRAY),
+ builtInTypeValue(new boolean[]{true, false}, boolean[].class, BuiltinType.BOOLEAN_ARRAY),
+ builtInTypeValue(new char[]{'a', 'b'}, char[].class, BuiltinType.CHAR_ARRAY),
+ builtInTypeValue(new Object[]{42, "123", null}, Object[].class, BuiltinType.OBJECT_ARRAY),
+ builtInTypeValue(new BitSet[]{BitSet.valueOf(new long[]{42, 43}), BitSet.valueOf(new long[]{1, 2}), null},
BitSet[].class, BuiltinType.OBJECT_ARRAY),
- builtInTypeValueArg(new String[]{"Ignite", "rulez"}, String[].class, BuiltinType.STRING_ARRAY),
- builtInTypeValueArg(new BigDecimal(42), BigDecimal.class, BuiltinType.DECIMAL),
- builtInTypeValueArg(new BigDecimal[]{new BigDecimal(42), new BigDecimal(43)}, BigDecimal[].class,
+ builtInTypeValue(new String[]{"Ignite", "rulez"}, String[].class, BuiltinType.STRING_ARRAY),
+ builtInTypeValue(new BigDecimal(42), BigDecimal.class, BuiltinType.DECIMAL),
+ builtInTypeValue(new BigDecimal[]{new BigDecimal(42), new BigDecimal(43)}, BigDecimal[].class,
BuiltinType.DECIMAL_ARRAY),
- builtInTypeValueArg(SimpleEnum.FIRST, SimpleEnum.class, BuiltinType.ENUM),
- builtInTypeValueArg(new Enum[]{SimpleEnum.FIRST, SimpleEnum.SECOND}, Enum[].class, BuiltinType.ENUM_ARRAY),
- builtInTypeValueArg(new SimpleEnum[]{SimpleEnum.FIRST, SimpleEnum.SECOND}, SimpleEnum[].class, BuiltinType.ENUM_ARRAY),
- builtInTypeValueArg(EnumWithAnonClassesForMembers.FIRST, EnumWithAnonClassesForMembers.class, BuiltinType.ENUM),
- builtInTypeValueArg(new Enum[]{EnumWithAnonClassesForMembers.FIRST, EnumWithAnonClassesForMembers.SECOND}, Enum[].class,
+ builtInTypeValue(SimpleEnum.FIRST, SimpleEnum.class, BuiltinType.ENUM),
+ builtInTypeValue(new Enum[]{SimpleEnum.FIRST, SimpleEnum.SECOND}, Enum[].class, BuiltinType.ENUM_ARRAY),
+ builtInTypeValue(new SimpleEnum[]{SimpleEnum.FIRST, SimpleEnum.SECOND}, SimpleEnum[].class, BuiltinType.ENUM_ARRAY),
+ builtInTypeValue(EnumWithAnonClassesForMembers.FIRST, EnumWithAnonClassesForMembers.class, BuiltinType.ENUM),
+ builtInTypeValue(new Enum[]{EnumWithAnonClassesForMembers.FIRST, EnumWithAnonClassesForMembers.SECOND}, Enum[].class,
BuiltinType.ENUM_ARRAY),
- builtInTypeValueArg(
+ builtInTypeValue(
new EnumWithAnonClassesForMembers[]{EnumWithAnonClassesForMembers.FIRST, EnumWithAnonClassesForMembers.SECOND},
EnumWithAnonClassesForMembers[].class,
BuiltinType.ENUM_ARRAY
),
- builtInTypeValueArg(BitSet.valueOf(new long[]{42, 43}), BitSet.class, BuiltinType.BIT_SET),
- builtInTypeValueArg(null, Void.class, BuiltinType.VOID)
- );
+ builtInTypeValue(BitSet.valueOf(new long[]{42, 43}), BitSet.class, BuiltinType.BIT_SET),
+ builtInTypeValue(null, Null.class, BuiltinType.NULL),
+ builtInTypeValue(null, Void.class, BuiltinType.VOID)
+ ).map(Arguments::of);
}
@ParameterizedTest
@@ -256,14 +238,14 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
static Stream<Arguments> builtInCollectionTypes() {
return Stream.of(
- builtInTypeValueArg(new ArrayList<>(List.of(42, 43)), ArrayList.class, BuiltinType.ARRAY_LIST),
- builtInTypeValueArg(new LinkedList<>(List.of(42, 43)), LinkedList.class, BuiltinType.LINKED_LIST),
- builtInTypeValueArg(new HashSet<>(Set.of(42, 43)), HashSet.class, BuiltinType.HASH_SET),
- builtInTypeValueArg(new LinkedHashSet<>(Set.of(42, 43)), LinkedHashSet.class, BuiltinType.LINKED_HASH_SET),
- builtInTypeValueArg(singletonList(42), BuiltinType.SINGLETON_LIST.clazz(), BuiltinType.SINGLETON_LIST),
- builtInTypeValueArg(new HashMap<>(Map.of(42, 43)), HashMap.class, BuiltinType.HASH_MAP),
- builtInTypeValueArg(new LinkedHashMap<>(Map.of(42, 43)), LinkedHashMap.class, BuiltinType.LINKED_HASH_MAP)
- );
+ builtInTypeValue(new ArrayList<>(List.of(42, 43)), ArrayList.class, BuiltinType.ARRAY_LIST),
+ builtInTypeValue(new LinkedList<>(List.of(42, 43)), LinkedList.class, BuiltinType.LINKED_LIST),
+ builtInTypeValue(new HashSet<>(Set.of(42, 43)), HashSet.class, BuiltinType.HASH_SET),
+ builtInTypeValue(new LinkedHashSet<>(Set.of(42, 43)), LinkedHashSet.class, BuiltinType.LINKED_HASH_SET),
+ builtInTypeValue(singletonList(42), BuiltinType.SINGLETON_LIST.clazz(), BuiltinType.SINGLETON_LIST),
+ builtInTypeValue(new HashMap<>(Map.of(42, 43)), HashMap.class, BuiltinType.HASH_MAP),
+ builtInTypeValue(new LinkedHashMap<>(Map.of(42, 43)), LinkedHashMap.class, BuiltinType.LINKED_HASH_MAP)
+ ).map(Arguments::of);
}
@ParameterizedTest
@@ -278,9 +260,64 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
return Stream.concat(builtInNonCollectionTypes(), builtInCollectionTypes());
}
- @NotNull
- private static Arguments builtInTypeValueArg(Object value, Class<?> valueClass, BuiltinType type) {
- return Arguments.of(new BuiltInTypeValue(value, valueClass, type));
+ private static BuiltInTypeValue builtInTypeValue(Object value, Class<?> valueClass, BuiltinType type) {
+ return new BuiltInTypeValue(value, valueClass, type);
+ }
+
+ @Test
+ void unmarshalsObjectGraphWithCycleStartingWithSingletonList() throws Exception {
+ List<List<?>> mutableList = new ArrayList<>();
+ List<List<?>> singletonList = singletonList(mutableList);
+ mutableList.add(singletonList);
+
+ List<List<?>> unmarshalled = marshalAndUnmarshal(singletonList);
+
+ assertThat(unmarshalled.get(0).get(0), is(sameInstance(unmarshalled)));
+ }
+
+ private <T> T marshalAndUnmarshal(T object) throws MarshalException, UnmarshalException {
+ MarshalledObject marshalled = marshaller.marshal(object);
+ return unmarshalNonNull(marshalled);
+ }
+
+ @Test
+ void unmarshalsObjectGraphWithCycleContainingWithSingletonList() throws Exception {
+ List<List<?>> mutableList = new ArrayList<>();
+ List<List<?>> singletonList = singletonList(mutableList);
+ mutableList.add(singletonList);
+
+ List<List<?>> unmarshalled = marshalAndUnmarshal(mutableList);
+
+ assertThat(unmarshalled.get(0).get(0), is(sameInstance(unmarshalled)));
+ }
+
+ @ParameterizedTest
+ @MethodSource("mutableContainerSelfAssignments")
+ <T> void unmarshalsObjectGraphWithSelfCycleViaMutableContainers(MutableContainerSelfAssignment<T> item) throws Exception {
+ T container = item.factory.get();
+ item.assignment.accept(container, container);
+
+ T unmarshalled = marshalAndUnmarshal(container);
+ T element = item.elementAccess.apply(unmarshalled);
+
+ assertThat(element, is(sameInstance(unmarshalled)));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Stream<Arguments> mutableContainerSelfAssignments() {
+ return Stream.of(
+ new MutableContainerSelfAssignment<>(Object[].class, () -> new Object[1], (a, b) -> a[0] = b, array -> (Object[]) array[0]),
+ new MutableContainerSelfAssignment<>(ArrayList.class, ArrayList::new, ArrayList::add, list -> (ArrayList<?>) list.get(0)),
+ new MutableContainerSelfAssignment<>(LinkedList.class, LinkedList::new, LinkedList::add,
+ list -> (LinkedList<?>) list.get(0)),
+ new MutableContainerSelfAssignment<>(HashSet.class, HashSet::new, HashSet::add, set -> (HashSet<?>) set.iterator().next()),
+ new MutableContainerSelfAssignment<>(LinkedHashSet.class, LinkedHashSet::new, LinkedHashSet::add,
+ set -> (LinkedHashSet<?>) set.iterator().next()),
+ new MutableContainerSelfAssignment<>(HashMap.class, HashMap::new, (map, el) -> map.put(el, el),
+ map -> (HashMap<?, ?>) map.values().iterator().next()),
+ new MutableContainerSelfAssignment<>(LinkedHashMap.class, LinkedHashMap::new, (map, el) -> map.put(el, el),
+ map -> (LinkedHashMap<?, ?>) map.values().iterator().next())
+ ).map(Arguments::of);
}
private enum SimpleEnum {
@@ -315,4 +352,30 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
+ '}';
}
}
+
+ private static class MutableContainerSelfAssignment<T> {
+ private final Class<T> clazz;
+ private final Supplier<T> factory;
+ private final BiConsumer<T, T> assignment;
+ private final Function<T, T> elementAccess;
+
+ private MutableContainerSelfAssignment(
+ Class<T> clazz,
+ Supplier<T> factory,
+ BiConsumer<T, T> assignment,
+ Function<T, T> elementAccess
+ ) {
+ this.clazz = clazz;
+ this.factory = factory;
+ this.assignment = assignment;
+ this.elementAccess = elementAccess;
+ }
+
+ @Override
+ public String toString() {
+ return "ContainerSelfCycle{"
+ + "clazz=" + clazz
+ + '}';
+ }
+ }
}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContextTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContextTest.java
new file mode 100644
index 0000000..ec341c6
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingContextTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.Objects;
+import org.junit.jupiter.api.Test;
+
+class MarshallingContextTest {
+ private final MarshallingContext context = new MarshallingContext();
+
+ @Test
+ void firstMemorizationReturnsFalse() {
+ assertNull(context.rememberAsSeen(new Object()));
+ }
+
+ @Test
+ void secondMemorizationOfSameObjectReturnsTrue() {
+ Object object = new Object();
+
+ context.rememberAsSeen(object);
+
+ assertNotNull(context.rememberAsSeen(object));
+ }
+
+ @Test
+ void differentInstancesAreNotSameForMemorizationEvenWhenEqual() {
+ Key key1 = new Key("test");
+ Key key2 = new Key("test");
+
+ context.rememberAsSeen(key1);
+
+ assertNull(context.rememberAsSeen(key2));
+ }
+
+ @Test
+ void ignoresNulls() {
+ context.rememberAsSeen(null);
+
+ assertNull(context.rememberAsSeen(null));
+ }
+
+ private static class Key {
+ private final String key;
+
+ private Key(String key) {
+ this.key = key;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Key key1 = (Key) o;
+ return Objects.equals(key, key1.key);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(key);
+ }
+ }
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/NoArgConstructorInstantiationTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/NoArgConstructorInstantiationTest.java
new file mode 100644
index 0000000..3357f13
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/NoArgConstructorInstantiationTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link NoArgConstructorInstantiation}.
+ */
+class NoArgConstructorInstantiationTest {
+ private final Instantiation instantiation = new NoArgConstructorInstantiation();
+
+ @Test
+ void supportsClassesHavingAccessibleNoArgConstructor() {
+ assertTrue(instantiation.supports(WithAccessibleNoArgConstructor.class));
+ }
+
+ @Test
+ void supportsClassesHavingPrivateNoArgConstructor() {
+ assertTrue(instantiation.supports(WithPrivateNoArgConstructor.class));
+ }
+
+ @Test
+ void doesNotSupportClassesWithoutNoArgConstructor() {
+ assertFalse(instantiation.supports(WithoutNoArgConstructor.class));
+ }
+
+ @Test
+ void instantiatesClassesHavingAccessibleNoArgConstructor() throws Exception {
+ Object instance = instantiation.newInstance(WithAccessibleNoArgConstructor.class);
+
+ assertThat(instance, is(notNullValue()));
+ }
+
+ @Test
+ void instantiatesClassesHavingPrivateNoArgConstructor() throws Exception {
+ Object instance = instantiation.newInstance(WithPrivateNoArgConstructor.class);
+
+ assertThat(instance, is(notNullValue()));
+ }
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java
new file mode 100644
index 0000000..fbcb148
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/SerializableInstantiationTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.lenient;
+
+import java.io.Serializable;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.apache.ignite.internal.network.serialization.ClassIndexedDescriptors;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * Tests for {@link SerializableInstantiation}.
+ */
+@ExtendWith(MockitoExtension.class)
+class SerializableInstantiationTest {
+ @Mock
+ private ClassIndexedDescriptors descriptors;
+ @Mock
+ private ClassDescriptor descriptor;
+
+ private Instantiation instantiation;
+
+ @BeforeEach
+ void initMocks() {
+ lenient().when(descriptors.getRequiredDescriptor(any())).thenReturn(descriptor);
+ }
+
+ @BeforeEach
+ void createInstantiation() {
+ instantiation = new SerializableInstantiation(descriptors);
+ }
+
+ @Test
+ void doesNotSupportNonSerializableClasses() {
+ assertFalse(instantiation.supports(NotSerializable.class));
+ }
+
+ @Test
+ void supportsSerializableClasses() {
+ assertTrue(instantiation.supports(SerializableWithoutNoArgConstructor.class));
+ }
+
+ @Test
+ void doesNotSupportClassesWithWriteReplace() {
+ doReturn(true).when(descriptor).hasWriteReplace();
+
+ assertFalse(instantiation.supports(WithWriteReplace.class));
+ }
+
+ @Test
+ void doesNotSupportClassesWithReadResolve() {
+ doReturn(true).when(descriptor).hasReadResolve();
+
+ assertFalse(instantiation.supports(WithReadResolve.class));
+ }
+
+ // TODO: IGNITE-16165 - test that it does not support instantiation of Serializable classes with writeObject()/readObject()
+
+ @Test
+ void instantiatesSerializableClassesWithoutNoArgConstructor() throws Exception {
+ Object instance = instantiation.newInstance(SerializableWithoutNoArgConstructor.class);
+
+ assertThat(instance, is(notNullValue()));
+ }
+
+ @Test
+ void instantiatesSerializableClassesWithFields() throws Exception {
+ Object instance = instantiation.newInstance(SerializableWithFields.class);
+
+ assertThat(instance, is(notNullValue()));
+ }
+
+ @Test
+ void instantiatesSerializableClassesWithNonSerializableParents() throws Exception {
+ Object instance = instantiation.newInstance(SerializableWithNonSerializableParent.class);
+
+ assertThat(instance, is(notNullValue()));
+ }
+
+ @Test
+ void instantiatesSerializableClassesWithSerializableParents() throws Exception {
+ Object instance = instantiation.newInstance(SerializableWithSerializableParent.class);
+
+ assertThat(instance, is(notNullValue()));
+ }
+
+ private static class NotSerializable {
+ }
+
+ private static class SerializableWithoutNoArgConstructor implements Serializable {
+ @SuppressWarnings("unused")
+ private SerializableWithoutNoArgConstructor(int ignored) {
+ }
+ }
+
+ private static class SerializableWithFields implements Serializable {
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final int value;
+
+ private SerializableWithFields(int value) {
+ this.value = value;
+ }
+ }
+
+ private static class NonSerializableParent {
+ public NonSerializableParent() {
+ }
+ }
+
+ private static class SerializableWithNonSerializableParent extends NonSerializableParent implements Serializable {
+ @SuppressWarnings("unused")
+ private SerializableWithNonSerializableParent(int ignored) {
+ }
+ }
+
+ private static class SerializableParent implements Serializable {
+ }
+
+ private static class SerializableWithSerializableParent extends SerializableParent implements Serializable {
+ @SuppressWarnings("unused")
+ private SerializableWithSerializableParent(int ignored) {
+ }
+ }
+
+ private static class WithWriteReplace implements Serializable {
+ private Object writeReplace() {
+ return this;
+ }
+ }
+
+ private static class WithReadResolve implements Serializable {
+ private Object readResolve() {
+ return this;
+ }
+ }
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/UnsafeInstantiationTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/UnsafeInstantiationTest.java
new file mode 100644
index 0000000..7beb536
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/UnsafeInstantiationTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class UnsafeInstantiationTest {
+ private final Instantiation instantiation = new UnsafeInstantiation();
+
+ @Test
+ void supportsClassesHavingNoArgConstructor() {
+ assertTrue(instantiation.supports(WithPrivateNoArgConstructor.class));
+ }
+
+ @Test
+ void supportsClassesWithoutNoArgConstructor() {
+ assertTrue(instantiation.supports(WithoutNoArgConstructor.class));
+ }
+
+ @Test
+ void instantiatesClassesHavingNoArgConstructor() throws Exception {
+ Object instance = instantiation.newInstance(WithPrivateNoArgConstructor.class);
+
+ assertThat(instance, is(notNullValue()));
+ }
+
+ @Test
+ void instantiatesClassesWithoutNoArgConstructor() throws Exception {
+ Object instance = instantiation.newInstance(WithoutNoArgConstructor.class);
+
+ assertThat(instance, is(notNullValue()));
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithAccessibleNoArgConstructor.java
similarity index 63%
copy from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
copy to modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithAccessibleNoArgConstructor.java
index d3d75b9..48a8949 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithAccessibleNoArgConstructor.java
@@ -17,20 +17,5 @@
package org.apache.ignite.internal.network.serialization.marshal;
-import java.io.DataOutput;
-import java.io.IOException;
-
-/**
- * Knows how to write a value to a {@link DataOutput}.
- */
-interface ValueWriter<T> {
- /**
- * Writes the given value to a {@link DataOutput}.
- *
- * @param value value to write
- * @param output where to write to
- * @throws IOException if an I/O problem occurs
- * @throws MarshalException if another problem occurs
- */
- void write(T value, DataOutput output) throws IOException, MarshalException;
+class WithAccessibleNoArgConstructor {
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithPrivateNoArgConstructor.java
similarity index 63%
copy from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
copy to modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithPrivateNoArgConstructor.java
index d3d75b9..913d9d2 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithPrivateNoArgConstructor.java
@@ -17,20 +17,7 @@
package org.apache.ignite.internal.network.serialization.marshal;
-import java.io.DataOutput;
-import java.io.IOException;
-
-/**
- * Knows how to write a value to a {@link DataOutput}.
- */
-interface ValueWriter<T> {
- /**
- * Writes the given value to a {@link DataOutput}.
- *
- * @param value value to write
- * @param output where to write to
- * @throws IOException if an I/O problem occurs
- * @throws MarshalException if another problem occurs
- */
- void write(T value, DataOutput output) throws IOException, MarshalException;
+class WithPrivateNoArgConstructor {
+ private WithPrivateNoArgConstructor() {
+ }
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java
similarity index 63%
copy from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
copy to modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java
index d3d75b9..0516176 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/ValueWriter.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java
@@ -17,20 +17,10 @@
package org.apache.ignite.internal.network.serialization.marshal;
-import java.io.DataOutput;
-import java.io.IOException;
+class WithoutNoArgConstructor {
+ int value;
-/**
- * Knows how to write a value to a {@link DataOutput}.
- */
-interface ValueWriter<T> {
- /**
- * Writes the given value to a {@link DataOutput}.
- *
- * @param value value to write
- * @param output where to write to
- * @throws IOException if an I/O problem occurs
- * @throws MarshalException if another problem occurs
- */
- void write(T value, DataOutput output) throws IOException, MarshalException;
+ public WithoutNoArgConstructor(int value) {
+ this.value = value;
+ }
}