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/18 14:54:03 UTC
[ignite-3] branch main updated: IGNITE-16240 Support putFields()+writeFields() and readFields() in User Object Serialization
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 a7d78f9 IGNITE-16240 Support putFields()+writeFields() and readFields() in User Object Serialization
a7d78f9 is described below
commit a7d78f9c597bcab939b89334525f69041d61d1c4
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Mon Jan 17 21:11:28 2022 +0400
IGNITE-16240 Support putFields()+writeFields() and readFields() in User Object Serialization
---
.../network/serialization/ClassDescriptor.java | 159 ++++++++++++-
.../network/serialization/FieldDescriptor.java | 21 ++
.../internal/network/serialization/Primitives.java | 52 ++++
.../network/serialization/marshal/Bits.java | 103 ++++++++
.../marshal/ExternalizableMarshaller.java | 23 +-
.../marshal/StructuredObjectMarshaller.java | 17 +-
.../marshal/UosObjectInputStream.java | 131 +++++++++++
.../marshal/UosObjectOutputStream.java | 138 ++++++++++-
.../network/serialization/PrimitivesTest.java | 58 +++++
...shallerWithSerializableOverrideStreamsTest.java | 262 ++++++++++++++++++++-
.../network/serialization/marshal/IntHolder.java | 29 +++
11 files changed, 965 insertions(+), 28 deletions(-)
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
index 247bf09..97602af 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
@@ -17,8 +17,14 @@
package org.apache.ignite.internal.network.serialization;
+import static java.util.stream.Collectors.toUnmodifiableMap;
+
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.lang.reflect.Modifier;
import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -59,6 +65,21 @@ public class ClassDescriptor {
*/
private final boolean isFinal;
+ /** Total number of bytes needed to store all primitive fields. */
+ private final int primitiveFieldsDataSize;
+ /** Total number of non-primitive fields. */
+ private final int objectFieldsCount;
+
+ private Map<String, FieldDescriptor> fieldsByName;
+ /**
+ * Offsets into primitive fields data array (which has size {@link #primitiveFieldsDataSize}).
+ * This array is a byte array containing data of all the primitive fields of an object.
+ * (Not to be confused with the offsets used in the context of {@link sun.misc.Unsafe}).
+ */
+ private Object2IntMap<String> primitiveFieldDataOffsets;
+ /** Indices of non-primitive fields in the object fields array. */
+ private Object2IntMap<String> objectFieldIndices;
+
private final SpecialSerializationMethods serializationMethods;
/**
@@ -78,9 +99,28 @@ public class ClassDescriptor {
this.serialization = serialization;
this.isFinal = Modifier.isFinal(clazz.getModifiers());
+ primitiveFieldsDataSize = computePrimitiveFieldsDataSize(fields);
+ objectFieldsCount = computeObjectFieldsCount(fields);
+
serializationMethods = new SpecialSerializationMethodsImpl(this);
}
+ private static int computePrimitiveFieldsDataSize(List<FieldDescriptor> fields) {
+ int accumulatedBytes = 0;
+ for (FieldDescriptor fieldDesc : fields) {
+ if (fieldDesc.isPrimitive()) {
+ accumulatedBytes += Primitives.widthInBytes(fieldDesc.clazz());
+ }
+ }
+ return accumulatedBytes;
+ }
+
+ private static int computeObjectFieldsCount(List<FieldDescriptor> fields) {
+ return (int) fields.stream()
+ .filter(fieldDesc -> !fieldDesc.isPrimitive())
+ .count();
+ }
+
/**
* Returns descriptor id.
*
@@ -288,14 +328,6 @@ public class ClassDescriptor {
return serializationMethods;
}
- @Override
- public String toString() {
- return "ClassDescriptor{"
- + "className='" + className() + '\''
- + ", descriptorId=" + descriptorId
- + '}';
- }
-
/**
* Returns {@code true} if this descriptor describes same class as the given descriptor.
*
@@ -305,4 +337,115 @@ public class ClassDescriptor {
public boolean describesSameClass(ClassDescriptor other) {
return other.clazz() == clazz();
}
+
+ /**
+ * Returns total number of bytes needed to store all primitive fields.
+ *
+ * @return total number of bytes needed to store all primitive fields
+ */
+ public int primitiveFieldsDataSize() {
+ return primitiveFieldsDataSize;
+ }
+
+ /**
+ * Returns total number of object (i.e. non-primitive) fields.
+ *
+ * @return total number of object (i.e. non-primitive) fields
+ */
+ public int objectFieldsCount() {
+ return objectFieldsCount;
+ }
+
+ /**
+ * Return offset into primitive fields data (which has size {@link #primitiveFieldsDataSize()}).
+ * These are different from the offsets used in the context of {@link sun.misc.Unsafe}.
+ *
+ * @param fieldName primitive field name
+ * @param requiredType field type
+ * @return offset into primitive fields data
+ */
+ public int primitiveFieldDataOffset(String fieldName, Class<?> requiredType) {
+ assert requiredType.isPrimitive();
+
+ if (fieldsByName == null) {
+ fieldsByName = fieldsByNameMap(fields);
+ }
+
+ FieldDescriptor fieldDesc = fieldsByName.get(fieldName);
+ if (fieldDesc == null) {
+ throw new IllegalStateException("Did not find a field with name " + fieldName);
+ }
+ if (fieldDesc.clazz() != requiredType) {
+ throw new IllegalStateException("Field " + fieldName + " has type " + fieldDesc.clazz()
+ + ", but it was used as " + requiredType);
+ }
+
+ if (primitiveFieldDataOffsets == null) {
+ primitiveFieldDataOffsets = primitiveFieldDataOffsetsMap(fields);
+ }
+
+ assert primitiveFieldDataOffsets.containsKey(fieldName);
+
+ return primitiveFieldDataOffsets.getInt(fieldName);
+ }
+
+ private static Map<String, FieldDescriptor> fieldsByNameMap(List<FieldDescriptor> fields) {
+ return fields.stream()
+ .collect(toUnmodifiableMap(FieldDescriptor::name, Function.identity()));
+ }
+
+ private static Object2IntMap<String> primitiveFieldDataOffsetsMap(List<FieldDescriptor> fields) {
+ Object2IntMap<String> map = new Object2IntOpenHashMap<>();
+
+ int accumulatedOffset = 0;
+ for (FieldDescriptor fieldDesc : fields) {
+ if (fieldDesc.isPrimitive()) {
+ map.put(fieldDesc.name(), accumulatedOffset);
+ accumulatedOffset += Primitives.widthInBytes(fieldDesc.clazz());
+ }
+ }
+
+ return map;
+ }
+
+ /**
+ * Returns index of a non-primitive (i.e. object) field in the object fields array.
+ *
+ * @param fieldName object field name
+ * @return index of a non-primitive (i.e. object) field in the object fields array
+ */
+ public int objectFieldIndex(String fieldName) {
+ if (objectFieldIndices == null) {
+ objectFieldIndices = computeObjectFieldIndices(fields);
+ }
+
+ if (!objectFieldIndices.containsKey(fieldName)) {
+ throw new IllegalStateException("Did not find an object field with name " + fieldName);
+ }
+
+ return objectFieldIndices.getInt(fieldName);
+ }
+
+ private Object2IntMap<String> computeObjectFieldIndices(List<FieldDescriptor> fields) {
+ Object2IntMap<String> map = new Object2IntOpenHashMap<>();
+
+ int currentIndex = 0;
+ for (FieldDescriptor fieldDesc : fields) {
+ if (!fieldDesc.isPrimitive()) {
+ map.put(fieldDesc.name(), currentIndex);
+ currentIndex++;
+ }
+ }
+
+ return map;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "ClassDescriptor{"
+ + "className='" + className() + '\''
+ + ", descriptorId=" + descriptorId
+ + '}';
+ }
}
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 e3e4687..826a40b 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
@@ -100,6 +100,27 @@ public class FieldDescriptor {
}
/**
+ * Returns {@code true} if this field has a primitive type.
+ *
+ * @return {@code true} if this field has a primitive type
+ */
+ public boolean isPrimitive() {
+ return clazz.isPrimitive();
+ }
+
+ /**
+ * Returns width in bytes (that is, how many bytes a value of the field type takes) of the field type.
+ * If the field type is not primitive, throws an exception.
+ *
+ * @return width in bytes
+ */
+ public int primitiveWidthInBytes() {
+ assert isPrimitive();
+
+ return Primitives.widthInBytes(clazz);
+ }
+
+ /**
* Returns {@link FieldAccessor} for this field.
*
* @return {@link FieldAccessor} for this field
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/Primitives.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/Primitives.java
new file mode 100644
index 0000000..969ab1c
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/Primitives.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * Utils to work with primitives.
+ */
+public class Primitives {
+ /**
+ * Returns number of bytes a value of the given primtive type takes (1 for byte, 8 for long and so on).
+ *
+ * @param clazz primitive type
+ * @return number of bytes
+ * @throws IllegalArgumentException if the passed type is not primitive
+ */
+ public static int widthInBytes(Class<?> clazz) {
+ if (clazz == byte.class) {
+ return Byte.BYTES;
+ } else if (clazz == short.class) {
+ return Short.BYTES;
+ } else if (clazz == int.class) {
+ return Integer.BYTES;
+ } else if (clazz == long.class) {
+ return Long.BYTES;
+ } else if (clazz == float.class) {
+ return Float.BYTES;
+ } else if (clazz == double.class) {
+ return Double.BYTES;
+ } else if (clazz == char.class) {
+ return Character.BYTES;
+ } else if (clazz == boolean.class) {
+ return 1;
+ } else {
+ throw new IllegalArgumentException(clazz + " is not primitive");
+ }
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/Bits.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/Bits.java
new file mode 100644
index 0000000..ae5085c
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/Bits.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+/**
+ * Code for packing/unpacking primitive values to/from a byte array at specific offsets.
+ */
+class Bits {
+ static boolean getBoolean(byte[] b, int off) {
+ return b[off] != 0;
+ }
+
+ static char getChar(byte[] b, int off) {
+ return (char) ((b[off + 1] & 0xFF)
+ + (b[off] << 8));
+ }
+
+ static short getShort(byte[] b, int off) {
+ return (short) ((b[off + 1] & 0xFF)
+ + (b[off] << 8));
+ }
+
+ static int getInt(byte[] b, int off) {
+ return ((b[off + 3] & 0xFF))
+ + ((b[off + 2] & 0xFF) << 8)
+ + ((b[off + 1] & 0xFF) << 16)
+ + ((b[off]) << 24);
+ }
+
+ static float getFloat(byte[] b, int off) {
+ return Float.intBitsToFloat(getInt(b, off));
+ }
+
+ static long getLong(byte[] b, int off) {
+ return ((b[off + 7] & 0xFFL))
+ + ((b[off + 6] & 0xFFL) << 8)
+ + ((b[off + 5] & 0xFFL) << 16)
+ + ((b[off + 4] & 0xFFL) << 24)
+ + ((b[off + 3] & 0xFFL) << 32)
+ + ((b[off + 2] & 0xFFL) << 40)
+ + ((b[off + 1] & 0xFFL) << 48)
+ + (((long) b[off]) << 56);
+ }
+
+ static double getDouble(byte[] b, int off) {
+ return Double.longBitsToDouble(getLong(b, off));
+ }
+
+ static void putBoolean(byte[] b, int off, boolean val) {
+ b[off] = (byte) (val ? 1 : 0);
+ }
+
+ static void putChar(byte[] b, int off, char val) {
+ b[off + 1] = (byte) (val);
+ b[off] = (byte) (val >>> 8);
+ }
+
+ static void putShort(byte[] b, int off, short val) {
+ b[off + 1] = (byte) (val);
+ b[off] = (byte) (val >>> 8);
+ }
+
+ static void putInt(byte[] b, int off, int val) {
+ b[off + 3] = (byte) (val);
+ b[off + 2] = (byte) (val >>> 8);
+ b[off + 1] = (byte) (val >>> 16);
+ b[off] = (byte) (val >>> 24);
+ }
+
+ static void putFloat(byte[] b, int off, float val) {
+ putInt(b, off, Float.floatToIntBits(val));
+ }
+
+ static void putLong(byte[] b, int off, long val) {
+ b[off + 7] = (byte) (val);
+ b[off + 6] = (byte) (val >>> 8);
+ b[off + 5] = (byte) (val >>> 16);
+ b[off + 4] = (byte) (val >>> 24);
+ b[off + 3] = (byte) (val >>> 32);
+ b[off + 2] = (byte) (val >>> 40);
+ b[off + 1] = (byte) (val >>> 48);
+ b[off] = (byte) (val >>> 56);
+ }
+
+ static void putDouble(byte[] b, int off, double val) {
+ putLong(b, off, Double.doubleToLongBits(val));
+ }
+}
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
index ac18624..77a706a 100644
--- 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
@@ -21,7 +21,6 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.Externalizable;
import java.io.IOException;
-import java.io.ObjectInputStream;
import org.apache.ignite.internal.network.serialization.ClassDescriptor;
/**
@@ -53,12 +52,18 @@ class ExternalizableMarshaller {
private void externalizeTo(Externalizable externalizable, DataOutputStream output, MarshallingContext context)
throws IOException {
- context.endWritingWithWriteObject();
-
// Do not close the stream yet!
UosObjectOutputStream oos = context.objectOutputStream(output, typedValueWriter, defaultFieldsReaderWriter);
- externalizable.writeExternal(oos);
- oos.flush();
+
+ UosObjectOutputStream.UosPutField oldPut = oos.replaceCurrentPutFieldWithNull();
+ context.endWritingWithWriteObject();
+
+ try {
+ externalizable.writeExternal(oos);
+ oos.flush();
+ } finally {
+ oos.restoreCurrentPutFieldTo(oldPut);
+ }
}
@SuppressWarnings("unchecked")
@@ -72,14 +77,18 @@ class ExternalizableMarshaller {
<T extends Externalizable> void fillExternalizableFrom(DataInputStream input, T object, UnmarshallingContext context)
throws IOException, UnmarshalException {
+ // Do not close the stream yet!
+ UosObjectInputStream ois = context.objectInputStream(input, valueReader, defaultFieldsReaderWriter);
+
+ UosObjectInputStream.UosGetField oldGet = ois.replaceCurrentGetFieldWithNull();
context.endReadingWithReadObject();
- // Do not close the stream yet!
- ObjectInputStream ois = context.objectInputStream(input, valueReader, defaultFieldsReaderWriter);
try {
object.readExternal(ois);
} catch (ClassNotFoundException e) {
throw new UnmarshalException("Cannot unmarshal due to a missing class", e);
+ } finally {
+ ois.restoreCurrentGetFieldTo(oldGet);
}
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/StructuredObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/StructuredObjectMarshaller.java
index 2e6278a..50dc4af 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/StructuredObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/StructuredObjectMarshaller.java
@@ -20,7 +20,6 @@ package org.apache.ignite.internal.network.serialization.marshal;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
-import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -30,6 +29,8 @@ import org.apache.ignite.internal.network.serialization.FieldAccessor;
import org.apache.ignite.internal.network.serialization.FieldDescriptor;
import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
import org.apache.ignite.internal.network.serialization.SpecialMethodInvocationException;
+import org.apache.ignite.internal.network.serialization.marshal.UosObjectInputStream.UosGetField;
+import org.apache.ignite.internal.network.serialization.marshal.UosObjectOutputStream.UosPutField;
/**
* (Un)marshals objects that have structure (fields). These are {@link java.io.Serializable}s
@@ -93,17 +94,20 @@ class StructuredObjectMarshaller implements DefaultFieldsReaderWriter {
private void writeWithWriteObject(Object object, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
throws IOException, MarshalException {
+ // Do not close the stream yet!
+ UosObjectOutputStream oos = context.objectOutputStream(output, valueWriter, this);
+
+ UosPutField oldPut = oos.replaceCurrentPutFieldWithNull();
context.startWritingWithWriteObject(object, descriptor);
try {
- // Do not close the stream yet!
- UosObjectOutputStream oos = context.objectOutputStream(output, valueWriter, this);
descriptor.serializationMethods().writeObject(object, oos);
oos.flush();
} catch (SpecialMethodInvocationException e) {
throw new MarshalException("Cannot invoke writeObject()", e);
} finally {
context.endWritingWithWriteObject();
+ oos.restoreCurrentPutFieldTo(oldPut);
}
}
@@ -191,16 +195,19 @@ class StructuredObjectMarshaller implements DefaultFieldsReaderWriter {
ClassDescriptor descriptor,
UnmarshallingContext context
) throws IOException, UnmarshalException {
+ // Do not close the stream yet!
+ UosObjectInputStream ois = context.objectInputStream(input, valueReader, this);
+
+ UosGetField oldGet = ois.replaceCurrentGetFieldWithNull();
context.startReadingWithReadObject(object, descriptor);
try {
- // Do not close the stream yet!
- ObjectInputStream ois = context.objectInputStream(input, valueReader, this);
descriptor.serializationMethods().readObject(object, ois);
} catch (SpecialMethodInvocationException e) {
throw new UnmarshalException("Cannot invoke readObject()", e);
} finally {
context.endReadingWithReadObject();
+ ois.restoreCurrentGetFieldTo(oldGet);
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
index 85e198a..b679d3a 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectInputStream.java
@@ -17,9 +17,14 @@
package org.apache.ignite.internal.network.serialization.marshal;
+import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.apache.ignite.internal.network.serialization.FieldDescriptor;
+import org.apache.ignite.internal.network.serialization.Primitives;
/**
* {@link ObjectInputStream} specialization used by User Object Serialization.
@@ -30,6 +35,8 @@ class UosObjectInputStream extends ObjectInputStream {
private final DefaultFieldsReaderWriter defaultFieldsReaderWriter;
private final UnmarshallingContext context;
+ private UosGetField currentGet;
+
UosObjectInputStream(
DataInputStream input,
ValueReader<Object> valueReader,
@@ -184,6 +191,16 @@ class UosObjectInputStream extends ObjectInputStream {
/** {@inheritDoc} */
@Override
+ public GetField readFields() throws IOException {
+ if (currentGet == null) {
+ currentGet = new UosGetField(context.descriptorOfObjectCurrentlyReadWithReadObject());
+ currentGet.readFields();
+ }
+ return currentGet;
+ }
+
+ /** {@inheritDoc} */
+ @Override
public int available() throws IOException {
return input.available();
}
@@ -197,5 +214,119 @@ class UosObjectInputStream extends ObjectInputStream {
/** {@inheritDoc} */
@Override
public void close() throws IOException {
+ // no-op
+ }
+
+ UosGetField replaceCurrentGetFieldWithNull() {
+ UosGetField oldGet = currentGet;
+ currentGet = null;
+ return oldGet;
+ }
+
+ void restoreCurrentGetFieldTo(UosGetField newGet) {
+ currentGet = newGet;
+ }
+
+ class UosGetField extends GetField {
+ private final DataInput input = UosObjectInputStream.this;
+ private final ClassDescriptor descriptor;
+
+ private final byte[] primitiveFieldsData;
+ private final Object[] objectFieldVals;
+
+ private UosGetField(ClassDescriptor currentObjectDescriptor) {
+ this.descriptor = currentObjectDescriptor;
+
+ primitiveFieldsData = new byte[currentObjectDescriptor.primitiveFieldsDataSize()];
+ objectFieldVals = new Object[currentObjectDescriptor.objectFieldsCount()];
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ObjectStreamClass getObjectStreamClass() {
+ return ObjectStreamClass.lookupAny(descriptor.clazz());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean defaulted(String name) throws IOException {
+ // TODO: IGNITE-15948 - actually take into account whether it's defaulted or not
+ return false;
+ }
+
+ // TODO: IGNITE-15948 - return default values if the field exists locally but not in the stream being parsed
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean get(String name, boolean val) throws IOException {
+ return Bits.getBoolean(primitiveFieldsData, primitiveFieldDataOffset(name, boolean.class));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public byte get(String name, byte val) throws IOException {
+ return primitiveFieldsData[primitiveFieldDataOffset(name, byte.class)];
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public char get(String name, char val) throws IOException {
+ return Bits.getChar(primitiveFieldsData, primitiveFieldDataOffset(name, char.class));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public short get(String name, short val) throws IOException {
+ return Bits.getShort(primitiveFieldsData, primitiveFieldDataOffset(name, short.class));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int get(String name, int val) throws IOException {
+ return Bits.getInt(primitiveFieldsData, primitiveFieldDataOffset(name, int.class));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long get(String name, long val) throws IOException {
+ return Bits.getLong(primitiveFieldsData, primitiveFieldDataOffset(name, long.class));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public float get(String name, float val) throws IOException {
+ return Bits.getFloat(primitiveFieldsData, primitiveFieldDataOffset(name, float.class));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double get(String name, double val) throws IOException {
+ return Bits.getDouble(primitiveFieldsData, primitiveFieldDataOffset(name, double.class));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object get(String name, Object val) throws IOException {
+ return objectFieldVals[descriptor.objectFieldIndex(name)];
+ }
+
+ private int primitiveFieldDataOffset(String fieldName, Class<?> requiredType) {
+ return descriptor.primitiveFieldDataOffset(fieldName, requiredType);
+ }
+
+ private void readFields() throws IOException {
+ int objectFieldIndex = 0;
+
+ for (FieldDescriptor fieldDesc : descriptor.fields()) {
+ if (fieldDesc.isPrimitive()) {
+ int offset = descriptor.primitiveFieldDataOffset(fieldDesc.name(), fieldDesc.clazz());
+ int length = Primitives.widthInBytes(fieldDesc.clazz());
+ input.readFully(primitiveFieldsData, offset, length);
+ } else {
+ objectFieldVals[objectFieldIndex] = doReadObject();
+ objectFieldIndex++;
+ }
+ }
+ }
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
index ad8e7f4..1334842 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/UosObjectOutputStream.java
@@ -21,7 +21,12 @@ import static org.apache.ignite.internal.network.serialization.marshal.ObjectCla
import java.io.DataOutputStream;
import java.io.IOException;
+import java.io.NotActiveException;
+import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
+import org.apache.ignite.internal.network.serialization.ClassDescriptor;
+import org.apache.ignite.internal.network.serialization.FieldDescriptor;
+import org.apache.ignite.internal.network.serialization.Primitives;
/**
* {@link ObjectOutputStream} specialization used by User Object Serialization.
@@ -32,6 +37,8 @@ class UosObjectOutputStream extends ObjectOutputStream {
private final DefaultFieldsReaderWriter defaultFieldsReaderWriter;
private final MarshallingContext context;
+ private UosPutField currentPut;
+
UosObjectOutputStream(
DataOutputStream output,
TypedValueWriter valueWriter,
@@ -131,10 +138,10 @@ class UosObjectOutputStream extends ObjectOutputStream {
/** {@inheritDoc} */
@Override
protected void writeObjectOverride(Object obj) throws IOException {
- writeObject0(obj);
+ doWriteObject(obj);
}
- private void writeObject0(Object obj) throws IOException {
+ private void doWriteObject(Object obj) throws IOException {
try {
valueWriter.write(obj, objectClass(obj), output, context);
} catch (MarshalException e) {
@@ -146,7 +153,7 @@ class UosObjectOutputStream extends ObjectOutputStream {
@Override
public void writeUnshared(Object obj) throws IOException {
// TODO: IGNITE-16257 - implement 'unshared' logic?
- writeObject0(obj);
+ doWriteObject(obj);
}
/** {@inheritDoc} */
@@ -166,6 +173,24 @@ class UosObjectOutputStream extends ObjectOutputStream {
/** {@inheritDoc} */
@Override
+ public PutField putFields() {
+ if (currentPut == null) {
+ currentPut = new UosPutField(context.descriptorOfObjectCurrentlyWrittenWithWriteObject());
+ }
+ return currentPut;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void writeFields() throws IOException {
+ if (currentPut == null) {
+ throw new NotActiveException("no current PutField object");
+ }
+ currentPut.write(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void useProtocolVersion(int version) {
// no op
}
@@ -188,4 +213,111 @@ class UosObjectOutputStream extends ObjectOutputStream {
public void close() throws IOException {
flush();
}
+
+ UosPutField replaceCurrentPutFieldWithNull() {
+ UosPutField oldPut = currentPut;
+ currentPut = null;
+ return oldPut;
+ }
+
+ void restoreCurrentPutFieldTo(UosPutField newPut) {
+ currentPut = newPut;
+ }
+
+ class UosPutField extends PutField {
+ private final ClassDescriptor descriptor;
+
+ private final byte[] primitiveFieldsData;
+ private final Object[] objectFieldVals;
+
+ private UosPutField(ClassDescriptor currentObjectDescriptor) {
+ this.descriptor = currentObjectDescriptor;
+
+ primitiveFieldsData = new byte[currentObjectDescriptor.primitiveFieldsDataSize()];
+ objectFieldVals = new Object[currentObjectDescriptor.objectFieldsCount()];
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void put(String name, boolean val) {
+ Bits.putBoolean(primitiveFieldsData, primitiveFieldDataOffset(name, boolean.class), val);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void put(String name, byte val) {
+ primitiveFieldsData[primitiveFieldDataOffset(name, byte.class)] = val;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void put(String name, char val) {
+ Bits.putChar(primitiveFieldsData, primitiveFieldDataOffset(name, char.class), val);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void put(String name, short val) {
+ Bits.putShort(primitiveFieldsData, primitiveFieldDataOffset(name, short.class), val);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void put(String name, int val) {
+ Bits.putInt(primitiveFieldsData, primitiveFieldDataOffset(name, int.class), val);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void put(String name, long val) {
+ Bits.putLong(primitiveFieldsData, primitiveFieldDataOffset(name, long.class), val);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void put(String name, float val) {
+ Bits.putFloat(primitiveFieldsData, primitiveFieldDataOffset(name, float.class), val);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void put(String name, double val) {
+ Bits.putDouble(primitiveFieldsData, primitiveFieldDataOffset(name, double.class), val);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void put(String name, Object val) {
+ objectFieldVals[objectFieldIndex(name)] = val;
+ }
+
+ private int primitiveFieldDataOffset(String fieldName, Class<?> requiredType) {
+ return descriptor.primitiveFieldDataOffset(fieldName, requiredType);
+ }
+
+ private int objectFieldIndex(String fieldName) {
+ return descriptor.objectFieldIndex(fieldName);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void write(ObjectOutput out) throws IOException {
+ if (out != UosObjectOutputStream.this) {
+ throw new IllegalArgumentException("This is not my output: " + out);
+ }
+
+ int objectFieldIndex = 0;
+
+ for (FieldDescriptor fieldDesc : descriptor.fields()) {
+ if (fieldDesc.isPrimitive()) {
+ int offset = primitiveFieldDataOffset(fieldDesc.name(), fieldDesc.clazz());
+ int length = Primitives.widthInBytes(fieldDesc.clazz());
+ out.write(primitiveFieldsData, offset, length);
+ } else {
+ doWriteObject(objectFieldVals[objectFieldIndex]);
+ objectFieldIndex++;
+ }
+ }
+ }
+ }
}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/PrimitivesTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/PrimitivesTest.java
new file mode 100644
index 0000000..7b99446
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/PrimitivesTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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 static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Tests for {@link Primitives}.
+ */
+class PrimitivesTest {
+ @ParameterizedTest
+ @MethodSource("primitiveByteWidths")
+ void widthInBytesIsCorrectForPrimitiveTypes(Class<?> type, int expectedWidth) {
+ assertThat(Primitives.widthInBytes(type), is(expectedWidth));
+ }
+
+ private static Stream<Arguments> primitiveByteWidths() {
+ return Stream.of(
+ Arguments.of(byte.class, 1),
+ Arguments.of(short.class, 2),
+ Arguments.of(int.class, 4),
+ Arguments.of(long.class, 8),
+ Arguments.of(float.class, 4),
+ Arguments.of(double.class, 8),
+ Arguments.of(char.class, 2),
+ Arguments.of(boolean.class, 1)
+ );
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @Test
+ void widthInBytesThrowsForNonPrimitiveTypes() {
+ assertThrows(IllegalArgumentException.class, () -> Primitives.widthInBytes(Object.class));
+ }
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java
index dc17421..0dd5706 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest.java
@@ -17,11 +17,15 @@
package org.apache.ignite.internal.network.serialization.marshal;
+import static org.apache.ignite.internal.network.serialization.marshal.Throwables.causalChain;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -57,7 +61,7 @@ class DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest {
private final DefaultUserObjectMarshaller marshaller = new DefaultUserObjectMarshaller(descriptorRegistry, descriptorFactory);
- /** This is static so that writeObject()/readObject() can easily find it. */
+ /** Reader+writer is static so that writeObject()/readObject() can easily find it. */
private static ReaderAndWriter<?> readerAndWriter;
/** Static access to the marshaller (for using in parameterized tests). */
@@ -65,6 +69,11 @@ class DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest {
/** Static access to the registry (for using in parameterized tests). */
private static ClassDescriptorRegistry staticDescriptorRegistry;
+ /** Putter is static so that writeObject() can easily find it. */
+ private static FieldPutter fieldPutter;
+ /** Filler is static so that readObject() can easily find it. */
+ private static FieldFiller fieldFiller;
+
@BeforeEach
void initStatics() {
staticMarshaller = marshaller;
@@ -203,9 +212,6 @@ class DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest {
).map(Arguments::of);
}
- // TODO: IGNITE-16240 - implement putFields()/writeFields()
- // TODO: IGNITE-16240 - implement readFields()
-
@SuppressWarnings("SameParameterValue")
private static byte[] readBytes(InputStream is, int count) throws IOException {
byte[] bytes = new byte[count];
@@ -254,6 +260,155 @@ class DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest {
}
@Test
+ void putFieldsWritesAllPrimitiveTypesAndObjectsSoThatDefaultUnmarshallingReadsThem() throws Exception {
+ fieldPutter = putField -> {
+ putField.put("byteVal", (byte) 101);
+ putField.put("shortVal", (short) 102);
+ putField.put("intVal", 103);
+ putField.put("longVal", (long) 104);
+ putField.put("floatVal", (float) 105);
+ putField.put("doubleVal", (double) 106);
+ putField.put("charVal", 'z');
+ putField.put("booleanVal", true);
+ putField.put("objectVal", new IntHolder(142));
+ };
+
+ WithPutFields unmarshalled = marshalAndUnmarshalNonNull(new WithPutFields());
+
+ assertThat(unmarshalled.byteVal, is((byte) 101));
+ assertThat(unmarshalled.shortVal, is((short) 102));
+ assertThat(unmarshalled.intVal, is(103));
+ assertThat(unmarshalled.longVal, is((long) 104));
+ assertThat(unmarshalled.floatVal, is((float) 105));
+ assertThat(unmarshalled.doubleVal, is((double) 106));
+ assertThat(unmarshalled.charVal, is('z'));
+ assertThat(unmarshalled.booleanVal, is(true));
+ assertThat(unmarshalled.objectVal, is(new IntHolder(142)));
+ }
+
+ @Test
+ void putFieldsWritesDefaultValuesForFieldsNotSpecifiedExplicitly() throws Exception {
+ fieldPutter = putField -> {
+ // do not put anything -> defaults should be written
+ };
+
+ WithPutFields unmarshalled = marshalAndUnmarshalNonNull(new WithPutFields());
+
+ assertThat(unmarshalled.byteVal, is((byte) 0));
+ assertThat(unmarshalled.shortVal, is((short) 0));
+ assertThat(unmarshalled.intVal, is(0));
+ assertThat(unmarshalled.longVal, is((long) 0));
+ assertThat(unmarshalled.floatVal, is((float) 0));
+ assertThat(unmarshalled.doubleVal, is((double) 0));
+ assertThat(unmarshalled.charVal, is('\0'));
+ assertThat(unmarshalled.booleanVal, is(false));
+ assertThat(unmarshalled.objectVal, is(nullValue()));
+ }
+
+ @Test
+ void putFieldsThrowsForAnUnknownFieldAccess() {
+ fieldPutter = putField -> putField.put("no-such-field", 1);
+
+ Exception exception = assertThrows(Exception.class, () -> marshalAndUnmarshalNonNull(new WithPutFields()));
+ assertThat(causalChain(exception), hasItem(hasProperty("message", equalTo("Did not find a field with name no-such-field"))));
+ }
+
+ @Test
+ void readFieldsReadsAllPrimitiveTypesAndObjectsWrittenWithDefaultMarshalling() throws Exception {
+ WithReadFields original = new WithReadFields();
+ original.byteVal = (byte) 101;
+ original.shortVal = (short) 102;
+ original.intVal = 103;
+ original.longVal = (long) 104;
+ original.floatVal = (float) 105;
+ original.doubleVal = (double) 106;
+ original.charVal = 'z';
+ original.booleanVal = true;
+ original.objectVal = new IntHolder(142);
+
+ fieldFiller = (getField, target) -> {
+ target.byteVal = getField.get("byteVal", (byte) 201);
+ target.shortVal = getField.get("shortVal", (short) 202);
+ target.intVal = getField.get("intVal", 203);
+ target.longVal = getField.get("longVal", (long) 204);
+ target.floatVal = getField.get("floatVal", (float) 205);
+ target.doubleVal = getField.get("doubleVal", (double) 206);
+ target.charVal = getField.get("charVal", '!');
+ target.booleanVal = getField.get("booleanVal", false);
+ target.objectVal = getField.get("objectVal", new IntHolder(242));
+ };
+
+ WithReadFields unmarshalled = marshalAndUnmarshalNonNull(original);
+
+ assertThat(unmarshalled.byteVal, is((byte) 101));
+ assertThat(unmarshalled.shortVal, is((short) 102));
+ assertThat(unmarshalled.intVal, is(103));
+ assertThat(unmarshalled.longVal, is((long) 104));
+ assertThat(unmarshalled.floatVal, is((float) 105));
+ assertThat(unmarshalled.doubleVal, is((double) 106));
+ assertThat(unmarshalled.charVal, is('z'));
+ assertThat(unmarshalled.booleanVal, is(true));
+ assertThat(unmarshalled.objectVal, is(new IntHolder(142)));
+ }
+
+ @Test
+ void whenReadFieldsIsUsedTheDefaultMechanismFillsNoFieldsItself() throws Exception {
+ fieldFiller = (getField, target) -> {
+ // do not fill anything, so default values must remain
+ };
+
+ WithReadFields unmarshalled = marshalAndUnmarshalNonNull(new WithReadFields());
+
+ assertThat(unmarshalled.byteVal, is((byte) 0));
+ assertThat(unmarshalled.shortVal, is((short) 0));
+ assertThat(unmarshalled.intVal, is(0));
+ assertThat(unmarshalled.longVal, is((long) 0));
+ assertThat(unmarshalled.floatVal, is((float) 0));
+ assertThat(unmarshalled.doubleVal, is((double) 0));
+ assertThat(unmarshalled.charVal, is('\0'));
+ assertThat(unmarshalled.booleanVal, is(false));
+ assertThat(unmarshalled.objectVal, is(nullValue()));
+ }
+
+ @Test
+ void getFieldsThrowsForAnUnknownFieldAccess() {
+ fieldFiller = (getField, target) -> getField.get("no-such-field", 1);
+
+ Exception exception = assertThrows(Exception.class, () -> marshalAndUnmarshalNonNull(new WithReadFields()));
+ assertThat(causalChain(exception), hasItem(hasProperty("message", equalTo("Did not find a field with name no-such-field"))));
+ }
+
+ @Test
+ void getFieldAlwaysReturnsFalseForDefaulted() {
+ // TODO: IGNITE-15948 - test that defaulted() works as intended when it's ready
+
+ fieldFiller = (getField, target) -> {
+ assertFalse(getField.defaulted("byteVal"));
+ };
+
+ assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(new WithReadFields()));
+ }
+
+ @Test
+ void nestedPutReadFieldsAreSupported() throws Exception {
+ NestHostWithPutGetFields unmarshalled = marshalAndUnmarshalNonNull(new NestHostWithPutGetFields());
+
+ assertThat(unmarshalled.intValue, is(2));
+ assertThat(unmarshalled.objectValue, is(12));
+ assertThat(unmarshalled.nested.intValue, is(1));
+ assertThat(unmarshalled.nested.objectValue, is(11));
+ }
+
+ @Test
+ void readFieldsObjectStreamClassIsAccessible() {
+ fieldFiller = (getField, target) -> {
+ assertThat(getField.getObjectStreamClass().getName(), is(equalTo(WithReadFields.class.getName())));
+ };
+
+ assertDoesNotThrow(() -> marshalAndUnmarshalNonNull(new WithReadFields()));
+ }
+
+ @Test
void supportsFlushInsideWriteObject() {
readerAndWriter = new ReaderAndWriter<>(ObjectOutputStream::flush, ois -> null);
@@ -265,7 +420,7 @@ class DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest {
}
@Test
- void resetThrowsInsideWriteObject() {
+ void resetErrorsOutInsideWriteObject() {
readerAndWriter = new ReaderAndWriter<>(ObjectOutputStream::reset, ois -> null);
assertThrows(MarshalException.class, this::marshalAndUnmarshalWithCustomizableOverride);
@@ -439,6 +594,103 @@ class DefaultUserObjectMarshallerWithSerializableOverrideStreamsTest {
}
}
+ private interface FieldPutter {
+ void putWith(ObjectOutputStream.PutField putField);
+ }
+
+ @SuppressWarnings("FieldMayBeFinal")
+ private static class WithPutFields implements Serializable {
+ private byte byteVal = 1;
+ private short shortVal = 2;
+ private int intVal = 3;
+ private long longVal = 4;
+ private float floatVal = 5;
+ private double doubleVal = 6;
+ private char charVal = 'a';
+ private boolean booleanVal = true;
+ private Object objectVal = new IntHolder(42);
+
+ private void writeObject(ObjectOutputStream oos) throws IOException {
+ ObjectOutputStream.PutField putField = oos.putFields();
+ fieldPutter.putWith(putField);
+ oos.writeFields();
+ }
+
+ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+ ois.defaultReadObject();
+ }
+ }
+
+ private interface FieldFiller {
+ void fillWith(ObjectInputStream.GetField getField, WithReadFields target) throws IOException;
+ }
+
+ private static class WithReadFields implements Serializable {
+ private byte byteVal = 1;
+ private short shortVal = 2;
+ private int intVal = 3;
+ private long longVal = 4;
+ private float floatVal = 5;
+ private double doubleVal = 6;
+ private char charVal = 'a';
+ private boolean booleanVal = true;
+ private Object objectVal = new IntHolder(42);
+
+ private void writeObject(ObjectOutputStream oos) throws IOException {
+ oos.defaultWriteObject();
+ }
+
+ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+ ObjectInputStream.GetField getField = ois.readFields();
+ fieldFiller.fillWith(getField, this);
+ }
+ }
+
+ private static class NestedWithPutFields implements Serializable {
+ private int intValue = 1;
+ private Object objectValue = 11;
+
+ private void writeObject(ObjectOutputStream stream) throws IOException {
+ ObjectOutputStream.PutField putField = stream.putFields();
+
+ putField.put("intValue", intValue);
+ putField.put("objectValue", objectValue);
+
+ stream.writeFields();
+ }
+
+ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+ ObjectInputStream.GetField getField = stream.readFields();
+
+ intValue = getField.get("intValue", -1);
+ objectValue = getField.get("objectValue", null);
+ }
+ }
+
+ private static class NestHostWithPutGetFields implements Serializable {
+ private int intValue = 2;
+ private Object objectValue = 12;
+ private NestedWithPutFields nested = new NestedWithPutFields();
+
+ private void writeObject(ObjectOutputStream stream) throws IOException {
+ ObjectOutputStream.PutField putField = stream.putFields();
+
+ putField.put("intValue", intValue);
+ putField.put("objectValue", objectValue);
+ putField.put("nested", nested);
+
+ stream.writeFields();
+ }
+
+ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+ ObjectInputStream.GetField getField = stream.readFields();
+
+ intValue = getField.get("intValue", -1);
+ objectValue = getField.get("objectValue", null);
+ nested = (NestedWithPutFields) getField.get("nested", null);
+ }
+ }
+
private static class SimpleNonSerializable {
private int value;
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/IntHolder.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/IntHolder.java
index 2a07715..dc10e31 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/IntHolder.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/IntHolder.java
@@ -17,6 +17,8 @@
package org.apache.ignite.internal.network.serialization.marshal;
+import java.util.Objects;
+
/**
* Holds an int value.
*/
@@ -26,4 +28,31 @@ class IntHolder {
IntHolder(int value) {
this.value = value;
}
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ IntHolder intHolder = (IntHolder) o;
+ return value == intHolder.value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "IntHolder{"
+ + "value=" + value
+ + '}';
+ }
}