You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2020/12/10 15:01:44 UTC
[ignite-3] 01/01: IGNITE-13618: Provide generated and
reflection-based class (de)serializers.
This is an automated email from the ASF dual-hosted git repository.
amashenkov pushed a commit to branch ignite-13618
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
commit 2cafc7b399875cf0ced4e1609ed2c76f6707dad0
Author: Andrew Mashenkov <an...@gmail.com>
AuthorDate: Thu Dec 10 18:01:26 2020 +0300
IGNITE-13618: Provide generated and reflection-based class (de)serializers.
Implemented reflection-based serializer using Unsafe.
Implemented generated serializer using Janino comp.
Benchmark for serializer added.
---
modules/commons/pom.xml | 41 ++
.../ignite/internal/schema/TupleAssembler.java | 4 -
.../internal/schema/marshaller/BinaryMode.java | 90 ++++
.../internal/schema/marshaller/MarshallerUtil.java | 103 ++++
.../schema/marshaller/SerializationException.java | 43 ++
.../internal/schema/marshaller/Serializer.java | 42 ++
.../schema/marshaller/SerializerFactory.java | 52 ++
.../generator/FieldAccessExprGenerator.java | 360 ++++++++++++++
.../IdentityObjectMarshallerExprGenerator.java | 45 ++
.../generator/JaninoSerializerGenerator.java | 346 ++++++++++++++
.../generator/MarshallerExprGenerator.java | 94 ++++
.../marshaller/reflection/JavaSerializer.java | 316 ++++++++++++
.../reflection/JavaSerializerFactory.java | 32 ++
.../schema/marshaller/reflection/Marshaller.java | 153 ++++++
.../marshaller/reflection/UnsafeFieldAccessor.java | 527 +++++++++++++++++++++
.../org/apache/ignite/internal/util/Factory.java | 32 ++
.../ignite/internal/util/IgniteUnsafeUtils.java | 273 +++++++++++
.../apache/ignite/internal/util/ObjectFactory.java | 62 +++
.../java/org/apache/ignite/internal/util/Pair.java | 57 +++
.../benchmarks/SerializerBenchmarkTest.java | 185 ++++++++
.../apache/ignite/internal/schema/TestUtils.java | 121 +++++
.../apache/ignite/internal/schema/TupleTest.java | 64 +--
.../schema/marshaller/JavaSerializerTest.java | 494 +++++++++++++++++++
.../marshaller/reflection/FieldAccessorTest.java | 396 ++++++++++++++++
pom.xml | 7 +-
25 files changed, 3871 insertions(+), 68 deletions(-)
diff --git a/modules/commons/pom.xml b/modules/commons/pom.xml
index 0599be4..fc78231 100644
--- a/modules/commons/pom.xml
+++ b/modules/commons/pom.xml
@@ -42,5 +42,46 @@
<artifactId>annotations</artifactId>
<version>${jetbrains.annotations.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.codehaus.janino</groupId>
+ <artifactId>janino</artifactId>
+ <version>${janino.version}</version>
+ </dependency>
+
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <version>${junit.jupiter.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Benchmarks dependencies -->
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-core</artifactId>
+ <version>${jmh.framework.verion}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-generator-annprocess</artifactId>
+ <version>${jmh.framework.verion}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <version>${javax.annotation.api.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/TupleAssembler.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/TupleAssembler.java
index 45d3982..71c3eda 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/TupleAssembler.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/TupleAssembler.java
@@ -17,12 +17,8 @@
package org.apache.ignite.internal.schema;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CoderResult;
import java.nio.charset.StandardCharsets;
import java.util.BitSet;
import java.util.UUID;
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/BinaryMode.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/BinaryMode.java
new file mode 100644
index 0000000..db5a77f
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/BinaryMode.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.schema.marshaller;
+
+import org.apache.ignite.internal.schema.NativeTypeSpec;
+
+/**
+ * Various read/write modes for binary objects that maps Java types to binary types.
+ */
+public enum BinaryMode {
+ /** Primitive byte. */
+ P_BYTE(NativeTypeSpec.BYTE),
+
+ /** Primitive short. */
+ P_SHORT(NativeTypeSpec.SHORT),
+
+ /** Primitive int. */
+ P_INT(NativeTypeSpec.INTEGER),
+
+ /** Primitive long. */
+ P_LONG(NativeTypeSpec.LONG),
+
+ /** Primitive float. */
+ P_FLOAT(NativeTypeSpec.FLOAT),
+
+ /** Primitive int. */
+ P_DOUBLE(NativeTypeSpec.DOUBLE),
+
+ /** */
+ BYTE(NativeTypeSpec.BYTE),
+
+ /** */
+ SHORT(NativeTypeSpec.SHORT),
+
+ /** */
+ INT(NativeTypeSpec.INTEGER),
+
+ /** */
+ LONG(NativeTypeSpec.LONG),
+
+ /** */
+ FLOAT(NativeTypeSpec.FLOAT),
+
+ /** */
+ DOUBLE(NativeTypeSpec.DOUBLE),
+
+ /** */
+ STRING(NativeTypeSpec.STRING),
+
+ /** */
+ UUID(NativeTypeSpec.UUID),
+
+ /** */
+ BYTE_ARR(NativeTypeSpec.BYTES),
+
+ /** */
+ BITSET(NativeTypeSpec.BITMASK);
+
+ /** Natove type spec. */
+ private final NativeTypeSpec typeSpec;
+
+ /**
+ * @param typeSpec Native type spec.
+ */
+ BinaryMode(NativeTypeSpec typeSpec) {
+ this.typeSpec = typeSpec;
+ }
+
+ /**
+ * @return Native type spec.
+ */
+ public NativeTypeSpec typeSpec() {
+ return typeSpec;
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/MarshallerUtil.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/MarshallerUtil.java
new file mode 100644
index 0000000..c111ba8
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/MarshallerUtil.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.schema.marshaller;
+
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.TupleAssembler;
+
+/**
+ * Marshaller utility class.
+ */
+public final class MarshallerUtil {
+ /**
+ * Calculates size for serialized value of varlen type.
+ *
+ * @param val Field value.
+ * @param type Mapped type.
+ * @return Serialized value size.
+ */
+ public static int getValueSize(Object val, NativeType type) {
+ switch (type.spec()) {
+ case BYTES:
+ return ((byte[])val).length;
+
+ case STRING:
+ return TupleAssembler.utf8EncodedLength((CharSequence)val);
+
+ default:
+ throw new IllegalStateException("Unsupported test varsize type: " + type);
+ }
+ }
+
+ /**
+ * Gets binary read/write mode for given class.
+ *
+ * @param cls Type.
+ * @return Binary mode.
+ */
+ public static BinaryMode mode(Class<?> cls) {
+ assert cls != null;
+
+ // Primitives.
+ if (cls == byte.class)
+ return BinaryMode.P_BYTE;
+ else if (cls == short.class)
+ return BinaryMode.P_SHORT;
+ else if (cls == int.class)
+ return BinaryMode.P_INT;
+ else if (cls == long.class)
+ return BinaryMode.P_LONG;
+ else if (cls == float.class)
+ return BinaryMode.P_FLOAT;
+ else if (cls == double.class)
+ return BinaryMode.P_DOUBLE;
+
+ // Boxed primitives.
+ else if (cls == Byte.class)
+ return BinaryMode.BYTE;
+ else if (cls == Short.class)
+ return BinaryMode.SHORT;
+ else if (cls == Integer.class)
+ return BinaryMode.INT;
+ else if (cls == Long.class)
+ return BinaryMode.LONG;
+ else if (cls == Float.class)
+ return BinaryMode.FLOAT;
+ else if (cls == Double.class)
+ return BinaryMode.DOUBLE;
+
+ // Other types
+ else if (cls == byte[].class)
+ return BinaryMode.BYTE_ARR;
+ else if (cls == String.class)
+ return BinaryMode.STRING;
+ else if (cls == UUID.class)
+ return BinaryMode.UUID;
+ else if (cls == BitSet.class)
+ return BinaryMode.BITSET;
+
+ return null;
+ }
+
+ /**
+ * Stub.
+ */
+ private MarshallerUtil() {}
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializationException.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializationException.java
new file mode 100644
index 0000000..e690710
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializationException.java
@@ -0,0 +1,43 @@
+/*
+ * 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.schema.marshaller;
+
+/**
+ * Serialization exception.
+ */
+public class SerializationException extends Exception {
+ /**
+ * Constructor.
+ *
+ * @param cause Cause.
+ */
+ public SerializationException(Throwable cause) {
+ // Used by serializers generated with Janino.
+ super(cause);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message Message.
+ * @param cause Cause.
+ */
+ public SerializationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/Serializer.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/Serializer.java
new file mode 100644
index 0000000..21a1560
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/Serializer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.schema.marshaller;
+
+/**
+ * Key-value objects (de)serializer.
+ */
+public interface Serializer {
+ /**
+ * Writes key-value pair to tuple.
+ *
+ * @param key Key object.
+ * @param val Value object.
+ * @return Serialized key-value pair.
+ */
+ byte[] serialize(Object key, Object val) throws SerializationException;
+
+ /**
+ * @return Key object.
+ */
+ Object deserializeKey(byte[] data) throws SerializationException;
+
+ /**
+ * @return Value object.
+ */
+ Object deserializeValue(byte[] data) throws SerializationException;
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializerFactory.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializerFactory.java
new file mode 100644
index 0000000..d9a8295
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializerFactory.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.schema.marshaller;
+
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.generator.JaninoSerializerGenerator;
+import org.apache.ignite.internal.schema.marshaller.reflection.JavaSerializerFactory;
+
+/**
+ * (De)Serializer factory interface.
+ */
+@FunctionalInterface
+public interface SerializerFactory {
+ /**
+ * @return Serializer factory back by code generator.
+ */
+ public static SerializerFactory createJaninoSerializerFactory() {
+ return new JaninoSerializerGenerator();
+ }
+
+ /**
+ * @return Reflection-based serializer factory.
+ */
+ public static SerializerFactory createJavaSerializerFactory() {
+ return new JavaSerializerFactory();
+ }
+
+ /**
+ * Creates serializer.
+ *
+ * @param schema Schema descriptor.
+ * @param keyClass Key class.
+ * @param valClass Value class.
+ * @return Serializer.
+ */
+ public Serializer create(SchemaDescriptor schema, Class<?> keyClass, Class<?> valClass);
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/FieldAccessExprGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/FieldAccessExprGenerator.java
new file mode 100644
index 0000000..fb3736a
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/FieldAccessExprGenerator.java
@@ -0,0 +1,360 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.schema.marshaller.generator.JaninoSerializerGenerator.LF;
+
+/**
+ * Object field access expression generators.
+ */
+class FieldAccessExprGenerator {
+ /** Append null expression. */
+ private static final String WRITE_NULL_EXPR = "asm.appendNull();";
+
+ /**
+ * Created object access expressions generator.
+ *
+ * @param mode Field access binary mode.
+ * @param colIdx Column absolute index in schema.
+ * @return Object field access expressions generator.
+ */
+ static FieldAccessExprGenerator createIdentityAccessor(BinaryMode mode, int colIdx) {
+ return createAccessor(mode, colIdx, -1L);
+ }
+
+ /**
+ * Created object field access expressions generator.
+ *
+ * @param mode Field access binary mode.
+ * @param colIdx Column absolute index in schema.
+ * @param offset Object field offset.
+ * @return Object field access expressions generator.
+ */
+ static FieldAccessExprGenerator createAccessor(BinaryMode mode, int colIdx, long offset) {
+ switch (mode) {
+ case BYTE:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "Byte",
+ "tuple.byteValueBoxed",
+ "asm.appendByte",
+ offset);
+
+ case P_BYTE:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "tuple.byteValue",
+ "asm.appendByte",
+ offset,
+ "IgniteUnsafeUtils.getByteField",
+ "IgniteUnsafeUtils.putByteField"
+ );
+
+ case SHORT:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "Short",
+ "tuple.shortValueBoxed",
+ "asm.appendShort",
+ offset);
+
+ case P_SHORT:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "tuple.shortValue",
+ "asm.appendShort",
+ offset,
+ "IgniteUnsafeUtils.getShortField",
+ "IgniteUnsafeUtils.putShortField"
+ );
+
+ case INT:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "Integer",
+ "tuple.intValueBoxed",
+ "asm.appendInt",
+ offset);
+
+ case P_INT:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "tuple.intValue",
+ "asm.appendInt",
+ offset,
+ "IgniteUnsafeUtils.getIntField",
+ "IgniteUnsafeUtils.putIntField"
+ );
+
+ case LONG:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "Long",
+ "tuple.longValueBoxed",
+ "asm.appendLong",
+ offset);
+
+ case P_LONG:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "tuple.longValue",
+ "asm.appendLong",
+ offset,
+ "IgniteUnsafeUtils.getLongField",
+ "IgniteUnsafeUtils.putLongField"
+ );
+
+ case FLOAT:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "Float",
+ "tuple.floatValueBoxed",
+ "asm.appendFloat",
+ offset);
+
+ case P_FLOAT:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "tuple.floatValue",
+ "asm.appendFloat",
+ offset,
+ "IgniteUnsafeUtils.getFloatField",
+ "IgniteUnsafeUtils.putFloatField"
+ );
+
+ case DOUBLE:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "Double",
+ "tuple.doubleValueBoxed",
+ "asm.appendDouble",
+ offset);
+
+ case P_DOUBLE:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "tuple.doubleValue",
+ "asm.appendDouble",
+ offset,
+ "IgniteUnsafeUtils.getDoubleField",
+ "IgniteUnsafeUtils.putDoubleField"
+ );
+
+ case UUID:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "UUID",
+ "tuple.uuidValue", "asm.appendUuid",
+ offset);
+
+ case BITSET:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "BitSet",
+ "tuple.bitmaskValue", "asm.appendBitmask",
+ offset);
+
+ case STRING:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "String",
+ "tuple.stringValue", "asm.appendString",
+ offset);
+
+ case BYTE_ARR:
+ return new FieldAccessExprGenerator(
+ colIdx,
+ "byte[]",
+ "tuple.bytesValue", "asm.appendBytes",
+ offset);
+ default:
+ throw new IllegalStateException("Unsupportd binary mode");
+ }
+ }
+
+ /** Object field offset or {@code -1} for identity accessor. */
+ private final long offset;
+
+ /** Absolute schema index. */
+ private final int colIdx;
+
+ /** Class cast expression. */
+ private final String classExpr;
+
+ /** Write column value expression. */
+ private final String writeColMethod;
+
+ /** Read column value expression. */
+ private final String readColMethod;
+
+ /** Read object field expression. */
+ private final String getFieldMethod;
+
+ /** Write object field expression. */
+ private final String putFieldMethod;
+
+ /**
+ * Constructor.
+ *
+ * @param colIdx Absolute schema index in schema.
+ * @param castClassExpr Class cast expression
+ * @param readColMethod Read column value expression.
+ * @param writeColMethod Write column value expression.
+ * @param offset Field offset or {@code -1} for identity accessor.
+ */
+ private FieldAccessExprGenerator(
+ int colIdx,
+ String castClassExpr,
+ String readColMethod,
+ String writeColMethod,
+ long offset
+ ) {
+ this(colIdx, castClassExpr, readColMethod, writeColMethod, offset,
+ "IgniteUnsafeUtils.getObjectField", "IgniteUnsafeUtils.putObjectField");
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param colIdx Absolute schema index in schema.
+ * @param readColMethod Read column value expression.
+ * @param writeColMethod Write column value expression.
+ * @param offset Field offset or {@code -1} for identity accessor.
+ * @param getFieldMethod Read object field expression.
+ * @param putFieldMethod Read object field expression.
+ */
+ public FieldAccessExprGenerator(
+ int colIdx,
+ String readColMethod,
+ String writeColMethod,
+ long offset,
+ String getFieldMethod,
+ String putFieldMethod
+ ) {
+ this(colIdx, null /* primitive type */, readColMethod, writeColMethod, offset, getFieldMethod, putFieldMethod);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param colIdx Absolute schema index in schema.
+ * @param castClassExpr Class cast expression or {@code null} if not applicable.
+ * @param readColMethod Read column value expression.
+ * @param writeColMethod Write column value expression.
+ * @param offset Field offset or {@code -1} for identity accessor.
+ * @param getFieldMethod Read object field expression.
+ * @param putFieldMethod Read object field expression.
+ */
+ private FieldAccessExprGenerator(
+ int colIdx,
+ @Nullable String castClassExpr,
+ String readColMethod,
+ String writeColMethod,
+ long offset,
+ String getFieldMethod,
+ String putFieldMethod
+ ) {
+ this.offset = offset;
+ this.colIdx = colIdx;
+ this.classExpr = castClassExpr;
+ this.putFieldMethod = putFieldMethod;
+ this.getFieldMethod = getFieldMethod;
+ this.writeColMethod = writeColMethod;
+ this.readColMethod = readColMethod;
+ }
+
+ /**
+ * @return {@code true} if it is primitive typed field accessor, {@code false} otherwise.
+ */
+ private boolean isPrimitive() {
+ return classExpr == null;
+ }
+
+ /**
+ * @return {@code true} if is identity accessor, {@code false} otherwise.
+ */
+ private boolean isIdentityAccessor() {
+ return offset == -1;
+ }
+
+ /**
+ * @return Object field value access expression or object value expression for simple types.
+ */
+ public String getFieldExpr() {
+ if (isIdentityAccessor())
+ return "obj"; // Identity accessor.
+
+ return getFieldMethod + "(obj, " + offset + ')';
+ }
+
+ /**
+ * Appends write value to field expression.
+ *
+ * @param sb String bulder.
+ * @param valueExpression Value expression.
+ * @param indent Line indentation.
+ */
+ public final void appendPutFieldExpr(StringBuilder sb, String valueExpression, String indent) {
+ sb.append(indent).append(putFieldMethod).append("(obj, ").append(offset).append(", ").append(valueExpression).append(')');
+ sb.append(";" + LF);
+ }
+
+ /**
+ * Appends write value to column expression.
+ *
+ * @param sb String bulder.
+ * @param valueExpr Value expression.
+ * @param indent Line indentation.
+ */
+ public final void appendWriteColumnExpr(StringBuilder sb, String valueExpr, String indent) {
+ if (isPrimitive() || isIdentityAccessor()) {
+ // Translate to:
+ // asm.appendX((T) %value%);
+ // or for primitive value:
+ // asm.appendX(%value%);
+ sb.append(indent).append(writeColMethod).append('(');
+
+ if (classExpr != null)
+ sb.append("(").append(classExpr).append(")");
+
+ sb.append(valueExpr).append(");" + LF);
+
+ return;
+ }
+
+ assert classExpr != null;
+
+ // Translate to:
+ // { T fVal = (T)%value%;
+ // if (fVal == null) asm.appendNull() else asm.appendX(fVal); }
+ sb.append(indent).append("{ ").append(classExpr).append(" fVal = (").append(classExpr).append(')').append(valueExpr).append(";" + LF);
+ sb.append(indent).append("if (fVal == null) " + WRITE_NULL_EXPR + LF);
+ sb.append(indent).append("else ").append(writeColMethod).append("(fVal); }" + LF);
+
+ }
+
+ /**
+ * @return Column value read expression.
+ */
+ public String readColumnExpr() {
+ return readColMethod + "(" + colIdx + ")";
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/IdentityObjectMarshallerExprGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/IdentityObjectMarshallerExprGenerator.java
new file mode 100644
index 0000000..e574e61
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/IdentityObjectMarshallerExprGenerator.java
@@ -0,0 +1,45 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+
+/**
+ * Generate {@link Serializer} method's bodies for simple types.
+ */
+class IdentityObjectMarshallerExprGenerator extends MarshallerExprGenerator {
+ /**
+ * Constructor.
+ *
+ * @param accessor Object field access expression generators.
+ */
+ IdentityObjectMarshallerExprGenerator(FieldAccessExprGenerator accessor) {
+ super(null /* no instantiation needed */, new FieldAccessExprGenerator[] {accessor});
+ }
+
+ /** {@inheritDoc} */
+ @Override public void appendMarshallObjectExpr(StringBuilder sb, String indent) {
+ for (int i = 0; i < accessors.length; i++)
+ accessors[i].appendWriteColumnExpr(sb, "obj", indent);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void appendUnmarshallObjectExpr(StringBuilder sb, String indent) {
+ sb.append(indent).append("Object obj = ").append(accessors[0].readColumnExpr()).append(";" + JaninoSerializerGenerator.LF);
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/JaninoSerializerGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/JaninoSerializerGenerator.java
new file mode 100644
index 0000000..654bf97
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/JaninoSerializerGenerator.java
@@ -0,0 +1,346 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.MarshallerUtil;
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+import org.apache.ignite.internal.schema.marshaller.SerializerFactory;
+import org.apache.ignite.internal.util.IgniteUnsafeUtils;
+import org.codehaus.commons.compiler.CompilerFactoryFactory;
+import org.codehaus.commons.compiler.IClassBodyEvaluator;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * {@link Serializer} code generator backed with Janino.
+ */
+public class JaninoSerializerGenerator implements SerializerFactory {
+ /** Tabulate. */
+ static final String TAB = " ";
+
+ /** Line feed. */
+ static final char LF = '\n';
+
+ /** String buffer initial size. */
+ public static final int INITIAL_BUFFER_SIZE = 8 * 1024;
+
+ /** Debug flag. */
+ private static final boolean enabledDebug = true;
+
+ /** {@inheritDoc} */
+ @Override public Serializer create(
+ SchemaDescriptor schema,
+ Class<?> keyClass,
+ Class<?> valClass
+ ) {
+ try {
+ final IClassBodyEvaluator ce = CompilerFactoryFactory.getDefaultCompilerFactory().newClassBodyEvaluator();
+
+ // Generate Serializer code.
+ String code = generateSerializerClassCode(ce, schema, keyClass, valClass);
+
+ //TODO: pass code to logger on trace level.
+
+ if (enabledDebug) {
+ ce.setDebuggingInformation(true, true, true);
+
+ //TODO: dump code to log.
+// System.out.println(code);
+ }
+
+ try { // Compile and load class.
+ ce.setParentClassLoader(getClass().getClassLoader());
+ ce.cook(code);
+
+ // Create and return Serializer instance.
+ final Constructor<Serializer> ctor = (Constructor<Serializer>)ce.getClazz()
+ .getDeclaredConstructor(schema.getClass(), Class.class, Class.class);
+
+ return ctor.newInstance(schema, keyClass, valClass);
+ }
+ catch (Exception ex) {
+ if (enabledDebug)
+ throw new IllegalStateException("Failed to compile/instantiate generated Serializer: code=" +
+ LF + code + LF, ex);
+ else
+ throw new IllegalStateException("Failed to compile/instantiate generated Serializer.", ex);
+ }
+ }
+ catch (Exception ex) {
+ //TODO: fallback to java serializer?
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ /**
+ * Generates serializer code.
+ *
+ * @param ce Class body evaluator.
+ * @param schema Schema descriptor.
+ * @param keyClass Key class.
+ * @param valClass Value class.
+ * @return Generated class code.
+ */
+ private String generateSerializerClassCode(
+ IClassBodyEvaluator ce,
+ SchemaDescriptor schema,
+ Class<?> keyClass,
+ Class<?> valClass
+ ) {
+ final String packageName = "org.apache.ignite.internal.schema.marshaller.";
+ final String className = "JaninoSerializerForSchema_" + schema.version();
+
+ // Prerequisites.
+ ce.setClassName(packageName + className);
+ ce.setImplementedInterfaces(new Class[] {Serializer.class});
+ ce.setDefaultImports(
+ "java.util.UUID",
+ "java.util.BitSet",
+
+ "org.apache.ignite.internal.schema.ByteBufferTuple",
+ "org.apache.ignite.internal.schema.Columns",
+ "org.apache.ignite.internal.schema.SchemaDescriptor",
+ "org.apache.ignite.internal.schema.Tuple",
+ "org.apache.ignite.internal.schema.TupleAssembler",
+ "org.apache.ignite.internal.util.IgniteUnsafeUtils",
+ "org.apache.ignite.internal.util.ObjectFactory"
+ );
+
+ // Build field accessor generators.
+ final MarshallerExprGenerator keyMarsh = createObjectMarshaller(keyClass, "keyFactory", schema.keyColumns(), 0);
+ final MarshallerExprGenerator valMarsh = createObjectMarshaller(valClass, "valFactory", schema.valueColumns(), schema.keyColumns().length());
+
+ // Create buffer.
+ final StringBuilder sb = new StringBuilder(INITIAL_BUFFER_SIZE);
+
+ // Append class fields desctiption.
+ sb.append("private final SchemaDescriptor schema;" + LF);
+
+ if (!keyMarsh.isSimpleTypeMarshaller())
+ sb.append("private final ObjectFactory keyFactory;" + LF);
+ if (!valMarsh.isSimpleTypeMarshaller())
+ sb.append("private final ObjectFactory valFactory;" + LF);
+
+ // Append constructor code.
+ sb.append(LF + "public ").append(className).append("(SchemaDescriptor schema, Class kClass, Class vClass) {" + LF);
+ sb.append(TAB + "this.schema = schema; " + LF);
+ if (!keyMarsh.isSimpleTypeMarshaller())
+ sb.append(TAB + "keyFactory = new ObjectFactory(kClass);" + LF);
+ if (!valMarsh.isSimpleTypeMarshaller())
+ sb.append(TAB + "valFactory = new ObjectFactory(vClass);" + LF);
+ sb.append("}" + LF);
+
+ // Generate and append helper-methods.
+ generateTupleFactoryMethod(sb, schema, keyMarsh, valMarsh);
+
+ // Generate and append Serializer interface methods.
+ appendSerializeMethod(sb, keyMarsh, valMarsh);
+ writeDeserializeKeyMethod(sb, keyMarsh);
+ writeDeserializeValueMethod(sb, valMarsh);
+
+ return sb.toString();
+ }
+
+ /**
+ * Creates marshal/unmarshall expressions generator for object.
+ *
+ * @param aClass Object class.
+ * @param factoryRefExpr Factory reference expression.
+ * @param columns Columns that aClass mapped to.
+ * @param firstColIdx First column absolute index in schema.
+ * @return Marshal/unmarshall expression generator.
+ */
+ private MarshallerExprGenerator createObjectMarshaller(
+ Class<?> aClass,
+ @Nullable String factoryRefExpr,
+ Columns columns,
+ int firstColIdx
+ ) {
+ BinaryMode mode = MarshallerUtil.mode(aClass);
+
+ if (mode != null)
+ return new IdentityObjectMarshallerExprGenerator(FieldAccessExprGenerator.createIdentityAccessor(mode, firstColIdx));
+
+ FieldAccessExprGenerator[] accessors = new FieldAccessExprGenerator[columns.length()];
+ try {
+ for (int i = 0; i < columns.length(); i++) {
+ final Field field = aClass.getDeclaredField(columns.column(i).name());
+
+ accessors[i] = FieldAccessExprGenerator.createAccessor(
+ MarshallerUtil.mode(field.getType()),
+ firstColIdx + i /* schma absolute index. */,
+ IgniteUnsafeUtils.objectFieldOffset(field));
+ }
+ }
+ catch (NoSuchFieldException ex) {
+ throw new IllegalStateException(ex);
+ }
+
+ return new MarshallerExprGenerator(factoryRefExpr, accessors);
+ }
+
+ /**
+ * Appends {@link Serializer#serialize(Object, Object)} method code.
+ *
+ * @param sb String buffer to append to.
+ * @param keyMarsh Marshall expression generator for key.
+ * @param valMarsh Marshall expression generator for value.
+ */
+ private void appendSerializeMethod(
+ StringBuilder sb,
+ MarshallerExprGenerator keyMarsh,
+ MarshallerExprGenerator valMarsh
+ ) {
+ // Mehtod signature.
+ sb.append(LF + "@Override public byte[] serialize(Object key, Object val) throws SerializationException {" + LF);
+ sb.append(TAB + "TupleAssembler asm = createAssembler(key, val);" + LF);
+
+ // Key marshal script.
+ sb.append(TAB + "{" + LF);
+ sb.append(TAB + TAB + "Object obj = key;" + LF);
+ keyMarsh.appendMarshallObjectExpr(sb, TAB + TAB);
+ sb.append(TAB + "}" + LF);
+
+ // Value marshal script.
+ sb.append(TAB + " {" + LF);
+ sb.append(TAB + TAB + "Object obj = val;" + LF);
+ valMarsh.appendMarshallObjectExpr(sb, TAB + TAB);
+ sb.append(TAB + "}" + LF);
+
+ // Return statement.
+ sb.append(TAB + "return asm.build();" + LF);
+ sb.append("}" + LF);
+ }
+
+ /**
+ * Appends {@link Serializer#deserializeKey(byte[])} method code.
+ *
+ * @param sb String buffer to append to.
+ * @param keyMarsh Unmarshall expression generator for key.
+ */
+ private void writeDeserializeKeyMethod(StringBuilder sb, MarshallerExprGenerator keyMarsh) {
+ // Mehtod signature.
+ sb.append(LF + "@Override public Object deserializeKey(byte[] data) throws SerializationException {" + LF);
+ sb.append(TAB + "Tuple tuple = new ByteBufferTuple(schema, data);" + LF);
+
+ // Key unmarshal script.
+ keyMarsh.appendUnmarshallObjectExpr(sb, TAB);
+
+ // Return statement.
+ sb.append(TAB + "return obj;" + LF);
+ sb.append("}" + LF);
+ }
+
+ /**
+ * Appends {@link Serializer#deserializeValue(byte[])} method code.
+ *
+ * @param sb String buffer to append to.
+ * @param valMarsh Unmarshall expression generator for value.
+ */
+ private void writeDeserializeValueMethod(StringBuilder sb, MarshallerExprGenerator valMarsh) {
+ // Mehtod signature.
+ sb.append(LF + "@Override public Object deserializeValue(byte[] data) throws SerializationException {" + LF);
+ sb.append(TAB + "Tuple tuple = new ByteBufferTuple(schema, data);" + LF);
+
+ // Key unmarshal script.
+ valMarsh.appendUnmarshallObjectExpr(sb, TAB);
+
+ // Return statement.
+ sb.append(TAB + "return obj;" + LF);
+ sb.append("}" + LF);
+ }
+
+ /**
+ * Appends helper methods code.
+ *
+ * @param sb String buffer to append to.
+ * @param schema Schema descriptor.
+ * @param keyMarsh Marshall expression generator for key.
+ * @param valMarsh Marshall expression generator for value.
+ */
+ private void generateTupleFactoryMethod(
+ StringBuilder sb,
+ SchemaDescriptor schema,
+ MarshallerExprGenerator keyMarsh,
+ MarshallerExprGenerator valMarsh
+ ) {
+ // Method signature.
+ sb.append(LF + "TupleAssembler createAssembler(Object key, Object val) {" + LF);
+ // Local variables.
+ sb.append(TAB + "int nonNullVarlenKeys = 0; int nonNullVarlenValues = 0;" + LF);
+ sb.append(TAB + "int nonNullVarlenKeysSize = 0; int nonNullVarlenValuesSize = 0;" + LF);
+ sb.append(LF);
+ sb.append(TAB + "Columns keyCols = schema.keyColumns();" + LF);
+ sb.append(TAB + "Columns valCols = schema.valueColumns();" + LF);
+ sb.append(LF);
+
+ Columns keyCols = schema.keyColumns();
+ if (keyCols.firstVarlengthColumn() >= 0) {
+ // Appends key analyzer code-block.
+ sb.append(TAB + "{" + LF);
+ sb.append(TAB + TAB + "Object fVal, obj = key;" + LF); // Temporary vars.
+
+ for (int i = keyCols.firstVarlengthColumn(); i < keyCols.length(); i++) {
+ assert !keyCols.column(i).type().spec().fixedLength();
+
+ sb.append(TAB + TAB + "assert !keyCols.column(").append(i).append(").type().spec().fixedLength();" + LF);
+ sb.append(TAB + TAB + "fVal = ").append(keyMarsh.accessors[i].getFieldExpr()).append(";" + LF);
+ sb.append(TAB + TAB + "if (fVal != null) {" + LF);
+ sb.append(TAB + TAB + TAB + "nonNullVarlenKeysSize += MarshallerUtil.getValueSize(fVal, keyCols.column(").append(i).append(").type());").append(LF);
+ sb.append(TAB + TAB + TAB + "nonNullVarlenKeys++;" + LF);
+ sb.append(TAB + TAB + "}" + LF);
+ }
+
+ sb.append(TAB + "}" + LF);
+ }
+
+ Columns valCols = schema.valueColumns();
+ if (valCols.firstVarlengthColumn() >= 0) {
+ // Appends value analyzer code-block.
+ sb.append(TAB + "{" + LF);
+ sb.append(TAB + TAB + "Object fVal, obj = val;" + LF); // Temporary vars.
+
+ for (int i = valCols.firstVarlengthColumn(); i < valCols.length(); i++) {
+ assert !valCols.column(i).type().spec().fixedLength();
+
+ sb.append(TAB + TAB + "assert !valCols.column(").append(i).append(").type().spec().fixedLength();" + LF);
+ sb.append(TAB + TAB + "fVal = ").append(valMarsh.accessors[i].getFieldExpr()).append(";" + LF);
+ sb.append(TAB + TAB + "if (fVal != null) {" + LF);
+ sb.append(TAB + TAB + TAB + "nonNullVarlenValuesSize += MarshallerUtil.getValueSize(fVal, valCols.column(").append(i).append(").type());" + LF);
+ sb.append(TAB + TAB + TAB + "nonNullVarlenValues++;" + LF);
+ sb.append(TAB + TAB + "}" + LF);
+ }
+ sb.append(TAB + "}" + LF);
+ }
+
+ // Calculate tuple size.
+ sb.append(LF);
+ sb.append(TAB + "int size = TupleAssembler.tupleSize(" + LF);
+ sb.append(TAB + TAB + "keyCols, nonNullVarlenKeys, nonNullVarlenKeysSize, " + LF);
+ sb.append(TAB + TAB + "valCols, nonNullVarlenValues, nonNullVarlenValuesSize); " + LF);
+ sb.append(LF);
+
+ // Return statement.
+ sb.append(TAB + "return new TupleAssembler(schema, size, nonNullVarlenKeys, nonNullVarlenValues);" + LF);
+ sb.append("}" + LF);
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerExprGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerExprGenerator.java
new file mode 100644
index 0000000..433b567
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerExprGenerator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+
+/**
+ * Generate {@link Serializer} method's bodies.
+ */
+class MarshallerExprGenerator {
+ /** Object factory regerence expression. */
+ private final String factoryRefExpr;
+
+ /** Object field access expression generators. */
+ protected FieldAccessExprGenerator[] accessors;
+
+ /**
+ * Constructor.
+ *
+ * @param factoryRefExpr Object factory regerence expression.
+ * @param accessors Object field access expression generators.
+ */
+ public MarshallerExprGenerator(String factoryRefExpr, FieldAccessExprGenerator[] accessors) {
+ this.accessors = accessors;
+ this.factoryRefExpr = factoryRefExpr;
+ }
+
+ /**
+ * @return {@code true} if it is simple object marshaller, {@code false} otherwise.
+ */
+ boolean isSimpleTypeMarshaller() {
+ return factoryRefExpr == null;
+ }
+
+ /**
+ * Appends unmashall object code to string builder.
+ *
+ * @param sb String builder.
+ * @param indent Line indentation.
+ */
+ public void appendUnmarshallObjectExpr(StringBuilder sb, String indent) {
+ assert factoryRefExpr != null;
+
+ sb.append(indent).append("Object obj;" + JaninoSerializerGenerator.LF);
+ // Try.
+ sb.append(indent).append("try {" + JaninoSerializerGenerator.LF);
+ sb.append(indent).append(JaninoSerializerGenerator.TAB + "obj = ").append(factoryRefExpr).append(".create();" + JaninoSerializerGenerator.LF);
+
+ // Read column from tuple to object field.
+ for (int i = 0; i < accessors.length; i++)
+ accessors[i].appendPutFieldExpr(sb, accessors[i].readColumnExpr(), indent + JaninoSerializerGenerator.TAB);
+
+ // Catch and rethrow wrapped exeption.
+ sb.append(indent).append("} catch (Exception ex) {" + JaninoSerializerGenerator.LF);
+ sb.append(indent).append(JaninoSerializerGenerator.TAB + "throw new SerializationException(\"Failed to instantiate object: \" + ")
+ .append(factoryRefExpr).append(".getClazz().getName(), ex);").append(JaninoSerializerGenerator.LF);
+ sb.append(indent).append("}" + JaninoSerializerGenerator.LF);
+ }
+
+ /**
+ * Appends mashall object code to string builder.
+ *
+ * @param sb String builder.
+ * @param indent Line indentation.
+ */
+ public void appendMarshallObjectExpr(StringBuilder sb, String indent) {
+ // Try.
+ sb.append(indent).append("try {" + JaninoSerializerGenerator.LF);
+
+ // Write object field to tuple assembler.
+ for (int i = 0; i < accessors.length; i++)
+ accessors[i].appendWriteColumnExpr(sb, accessors[i].getFieldExpr(), indent + JaninoSerializerGenerator.TAB);
+
+ // Catch and rethrow wrapped exeption.
+ sb.append(indent).append("} catch (Exception ex) {" + JaninoSerializerGenerator.LF);
+ sb.append(indent).append(JaninoSerializerGenerator.TAB + "throw new SerializationException(ex);").append(JaninoSerializerGenerator.LF);
+ sb.append(indent).append("}" + JaninoSerializerGenerator.LF);
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializer.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializer.java
new file mode 100644
index 0000000..4bbb630
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializer.java
@@ -0,0 +1,316 @@
+/*
+ * 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.schema.marshaller.reflection;
+
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.internal.schema.ByteBufferTuple;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.SerializationException;
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+
+import static org.apache.ignite.internal.schema.marshaller.MarshallerUtil.getValueSize;
+
+/**
+ * Reflection based (de)serializer.
+ */
+public class JavaSerializer implements Serializer {
+
+ /**
+ * Reads value object from tuple.
+ *
+ * @param reader Reader.
+ * @param colIdx Column index.
+ * @param mode Binary read mode.
+ * @return Read value object.
+ */
+ static Object readRefValue(Tuple reader, int colIdx, BinaryMode mode) {
+ assert reader != null;
+ assert colIdx >= 0;
+
+ Object val = null;
+
+ switch (mode) {
+ case BYTE:
+ val = reader.byteValueBoxed(colIdx);
+
+ break;
+
+ case SHORT:
+ val = reader.shortValueBoxed(colIdx);
+
+ break;
+
+ case INT:
+ val = reader.intValueBoxed(colIdx);
+
+ break;
+
+ case LONG:
+ val = reader.longValueBoxed(colIdx);
+
+ break;
+
+ case FLOAT:
+ val = reader.floatValueBoxed(colIdx);
+
+ break;
+
+ case DOUBLE:
+ val = reader.doubleValueBoxed(colIdx);
+
+ break;
+
+ case STRING:
+ val = reader.stringValue(colIdx);
+
+ break;
+
+ case UUID:
+ val = reader.uuidValue(colIdx);
+
+ break;
+
+ case BYTE_ARR:
+ val = reader.bytesValue(colIdx);
+
+ break;
+
+ case BITSET:
+ val = reader.bitmaskValue(colIdx);
+
+ break;
+
+ default:
+ assert false : "Invalid mode: " + mode;
+ }
+
+ return val;
+ }
+
+ /**
+ * Writes reference value to tuple.
+ *
+ * @param val Value object.
+ * @param writer Writer.
+ * @param mode Write binary mode.
+ */
+ static void writeRefObject(Object val, TupleAssembler writer, BinaryMode mode) {
+ assert writer != null;
+
+ if (val == null) {
+ writer.appendNull();
+
+ return;
+ }
+
+ switch (mode) {
+ case BYTE:
+ writer.appendByte((Byte)val);
+
+ break;
+
+ case SHORT:
+ writer.appendShort((Short)val);
+
+ break;
+
+ case INT:
+ writer.appendInt((Integer)val);
+
+ break;
+
+ case LONG:
+ writer.appendLong((Long)val);
+
+ break;
+
+ case FLOAT:
+ writer.appendFloat((Float)val);
+
+ break;
+
+ case DOUBLE:
+ writer.appendDouble((Double)val);
+
+ break;
+
+ case STRING:
+ writer.appendString((String)val);
+
+ break;
+
+ case UUID:
+ writer.appendUuid((UUID)val);
+
+ break;
+
+ case BYTE_ARR:
+ writer.appendBytes((byte[])val);
+
+ break;
+
+ case BITSET:
+ writer.appendBitmask((BitSet)val);
+
+ break;
+
+ default:
+ assert false : "Invalid mode: " + mode;
+ }
+ }
+
+ /** Schema. */
+ private final SchemaDescriptor schema;
+
+ /** Key class. */
+ private final Class<?> keyClass;
+
+ /** Value class. */
+ private final Class<?> valClass;
+
+ /** Key marshaller. */
+ private final Marshaller keyMarsh;
+
+ /** Value marshaller. */
+ private final Marshaller valMarsh;
+
+ /**
+ * Constructor.
+ *
+ * @param schema Schema.
+ * @param keyClass Key type.
+ * @param valClass Value type.
+ */
+ public JavaSerializer(SchemaDescriptor schema, Class<?> keyClass, Class<?> valClass) {
+ this.schema = schema;
+ this.keyClass = keyClass;
+ this.valClass = valClass;
+
+ keyMarsh = Marshaller.createMarshaller(schema.keyColumns(), 0, keyClass);
+ valMarsh = Marshaller.createMarshaller(schema.valueColumns(), schema.keyColumns().length(), valClass);
+ }
+
+ /** {@inheritDoc} */
+ @Override public byte[] serialize(Object key, Object val) throws SerializationException {
+ assert keyClass.isInstance(key);
+ assert val == null || valClass.isInstance(val);
+
+ final TupleAssembler asm = createAssembler(key, val);
+
+ keyMarsh.writeObject(key, asm);
+
+ if (val != null)
+ valMarsh.writeObject(val, asm);
+ else
+ assert false; // TODO: add tomstone support and remove assertion.
+
+ return asm.build();
+ }
+
+ /**
+ * Creates TupleAssebler for key-value pair.
+ *
+ * @param key Key object.
+ * @param val Value object.
+ * @return Tuple assembler.
+ */
+ private TupleAssembler createAssembler(Object key, Object val) {
+ ObjectStatistic keyStat = collectObjectStats(schema.keyColumns(), keyMarsh, key);
+ ObjectStatistic valStat = collectObjectStats(schema.valueColumns(), valMarsh, val);
+
+ int size = TupleAssembler.tupleSize(
+ schema.keyColumns(), keyStat.nonNullFields, keyStat.nonNullFieldsSize,
+ schema.valueColumns(), valStat.nonNullFields, valStat.nonNullFieldsSize);
+
+ return new TupleAssembler(schema, size, keyStat.nonNullFields, valStat.nonNullFields);
+ }
+
+ /**
+ * Reads object fields and gather statistic.
+ *
+ * @param cols Schema columns.
+ * @param marsh Marshaller.
+ * @param obj Object.
+ * @return Object statistic.
+ */
+ private ObjectStatistic collectObjectStats(Columns cols, Marshaller marsh, Object obj) {
+ if (obj == null || cols.firstVarlengthColumn() < 0 /* No varlen columns */)
+ return new ObjectStatistic(0, 0);
+
+ int cnt = 0;
+ int size = 0;
+
+ for (int i = cols.firstVarlengthColumn(); i < cols.length(); i++) {
+ final Object val = marsh.value(obj, i);
+
+ if (val == null || cols.column(i).type().spec().fixedLength())
+ continue;
+
+ size += getValueSize(val, cols.column(i).type());
+ cnt++;
+ }
+
+ return new ObjectStatistic(cnt, size);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object deserializeKey(byte[] data) throws SerializationException {
+ final Tuple tuple = new ByteBufferTuple(schema, data);
+
+ final Object o = keyMarsh.readObject(tuple);
+
+ assert keyClass.isInstance(o);
+
+ return o;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object deserializeValue(byte[] data) throws SerializationException {
+ final Tuple tuple = new ByteBufferTuple(schema, data);
+
+ // TODO: add tomstone support.
+
+ final Object o = valMarsh.readObject(tuple);
+
+ assert valClass.isInstance(o);
+
+ return o;
+ }
+
+ /**
+ * Object statistic.
+ */
+ private static class ObjectStatistic {
+ /** Non-null fields of varlen type. */
+ int nonNullFields;
+
+ /** Length of all non-null fields of varlen types. */
+ int nonNullFieldsSize;
+
+ /** Constructor. */
+ public ObjectStatistic(int nonNullFields, int nonNullFieldsSize) {
+ this.nonNullFields = nonNullFields;
+ this.nonNullFieldsSize = nonNullFieldsSize;
+ }
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializerFactory.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializerFactory.java
new file mode 100644
index 0000000..ec17ee4
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializerFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.schema.marshaller.reflection;
+
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+import org.apache.ignite.internal.schema.marshaller.SerializerFactory;
+
+/**
+ * Factory for reflection-based serializer.
+ */
+public class JavaSerializerFactory implements SerializerFactory {
+ /** {@inheritDoc} */
+ @Override public Serializer create(SchemaDescriptor schema, Class<?> keyClass, Class<?> valClass) {
+ return new JavaSerializer(schema, keyClass, valClass);
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/Marshaller.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/Marshaller.java
new file mode 100644
index 0000000..506c3a3
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/Marshaller.java
@@ -0,0 +1,153 @@
+/*
+ * 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.schema.marshaller.reflection;
+
+import java.util.Objects;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.MarshallerUtil;
+import org.apache.ignite.internal.schema.marshaller.SerializationException;
+import org.apache.ignite.internal.util.Factory;
+import org.apache.ignite.internal.util.ObjectFactory;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Marshaller.
+ */
+public class Marshaller {
+ /**
+ * Creates marshaller for class.
+ *
+ * @param cols Columns.
+ * @param firstColId First column position in schema.
+ * @param aClass Type.
+ * @return Marshaller.
+ */
+ public static Marshaller createMarshaller(Columns cols, int firstColId, Class<? extends Object> aClass) {
+ final BinaryMode mode = MarshallerUtil.mode(aClass);
+
+ if (mode != null) {
+ final Column col = cols.column(0);
+
+ assert cols.length() == 1;
+ assert mode.typeSpec() == col.type().spec() : "Target type is not compatible.";
+ assert !aClass.isPrimitive() : "Non-nullable types are not allowed.";
+
+ return new Marshaller(UnsafeFieldAccessor.createIdentityAccessor(col, firstColId, mode));
+ }
+
+ UnsafeFieldAccessor[] fieldAccessors = new UnsafeFieldAccessor[cols.length()];
+
+ // Build accessors
+ for (int i = 0; i < cols.length(); i++) {
+ final Column col = cols.column(i);
+
+ final int colIdx = firstColId + i; /* Absolute column idx in schema. */
+ fieldAccessors[i] = UnsafeFieldAccessor.create(aClass, col, colIdx);
+ }
+
+ return new Marshaller(new ObjectFactory<>(aClass), fieldAccessors);
+ }
+
+ /**
+ * Field accessors for mapped columns.
+ * Array has same size and order as columns.
+ */
+ private final UnsafeFieldAccessor[] fieldAccessors;
+
+ /**
+ * Object factory for complex types or {@code null} for basic type.
+ */
+ private final Factory<?> factory;
+
+ /**
+ * Constructor.
+ * Creates marshaller for complex types.
+ *
+ * @param factory Object factory.
+ * @param fieldAccessors Object field accessors for mapped columns.
+ */
+ @SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
+ public Marshaller(Factory<?> factory, UnsafeFieldAccessor[] fieldAccessors) {
+ this.fieldAccessors = fieldAccessors;
+ this.factory = Objects.requireNonNull(factory);
+ }
+
+ /**
+ * Constructor.
+ * Creates marshaller for basic types.
+ *
+ * @param fieldAccessor Identity field accessor for object of basic type.
+ */
+ public Marshaller(UnsafeFieldAccessor fieldAccessor) {
+ fieldAccessors = new UnsafeFieldAccessor[] {fieldAccessor};
+ factory = null;
+ }
+
+ /**
+ * Reads object field.
+ *
+ * @param obj Object.
+ * @param fldIdx Field index.
+ * @return Field value.
+ */
+ public @Nullable Object value(Object obj, int fldIdx) {
+ return fieldAccessors[fldIdx].value(obj);
+ }
+
+ /**
+ * Reads object from tuple.
+ *
+ * @param reader Tuple reader.
+ * @return Object.
+ * @throws SerializationException If failed.
+ */
+ public Object readObject(Tuple reader) throws SerializationException {
+ if (isBasicTypeMarshaller())
+ return fieldAccessors[0].read(reader);
+
+ final Object obj = factory.create();
+
+ for (int fldIdx = 0; fldIdx < fieldAccessors.length; fldIdx++)
+ fieldAccessors[fldIdx].read(obj, reader);
+
+ return obj;
+ }
+
+ /**
+ * Write object to tuple.
+ *
+ * @param obj Object.
+ * @param writer Tuple writer.
+ * @throws SerializationException If failed.
+ */
+ public void writeObject(Object obj, TupleAssembler writer) throws SerializationException {
+ for (int fldIdx = 0; fldIdx < fieldAccessors.length; fldIdx++)
+ fieldAccessors[fldIdx].write(obj, writer);
+ }
+
+ /**
+ * @return {@code true} if it is marshaller for basic type, {@code false} otherwise.
+ */
+ private boolean isBasicTypeMarshaller() {
+ return factory == null;
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/UnsafeFieldAccessor.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/UnsafeFieldAccessor.java
new file mode 100644
index 0000000..72b2491
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/UnsafeFieldAccessor.java
@@ -0,0 +1,527 @@
+/*
+ * 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.schema.marshaller.reflection;
+
+import java.lang.reflect.Field;
+import java.util.Objects;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.MarshallerUtil;
+import org.apache.ignite.internal.schema.marshaller.SerializationException;
+import org.apache.ignite.internal.util.IgniteUnsafeUtils;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Field accessor to speedup access.
+ */
+// TODO: Extract interface, move to java-8 profile and add Java9+ implementation using VarHandles.
+public abstract class UnsafeFieldAccessor {
+ /**
+ * TODO: implement sesitive information filtering.
+ *
+ * @return {@code False} if sensitive information exoising is prohibited, {@code false} otherwise.
+ */
+ private static boolean includeSensitive() {
+ return true;
+ }
+
+ /** Offset. */
+ protected final long offset;
+
+ /** Mode. */
+ protected final BinaryMode mode;
+
+ /** Field name */
+ protected final String name;
+
+ /**
+ * Mapped column position in schema.
+ * <p>
+ * NODE: Do not mix up with column index in {@link Columns} container.
+ */
+ protected final int colIdx;
+
+ /**
+ * Create accessor for the field.
+ *
+ * @param type Object class.
+ * @param col Mapped column.
+ * @param colIdx Column index in schema.
+ * @return Accessor.
+ */
+ static UnsafeFieldAccessor create(Class<?> type, Column col, int colIdx) {
+ try {
+ final Field field = type.getDeclaredField(col.name());
+
+ if (field.getType().isPrimitive() && col.nullable())
+ throw new IllegalArgumentException("Failed to map non-nullable field to nullable column [name=" + field.getName() + ']');
+
+ BinaryMode mode = MarshallerUtil.mode(field.getType());
+
+ switch (mode) {
+ case P_BYTE:
+ return new BytePrimitiveAccessor(field, colIdx);
+
+ case P_SHORT:
+ return new ShortPrimitiveAccessor(field, colIdx);
+
+ case P_INT:
+ return new IntPrimitiveAccessor(field, colIdx);
+
+ case P_LONG:
+ return new LongPrimitiveAccessor(field, colIdx);
+
+ case P_FLOAT:
+ return new FloatPrimitiveAccessor(field, colIdx);
+
+ case P_DOUBLE:
+ return new DoublePrimitiveAccessor(field, colIdx);
+
+ case BYTE:
+ case SHORT:
+ case INT:
+ case LONG:
+ case FLOAT:
+ case DOUBLE:
+ case STRING:
+ case UUID:
+ case BYTE_ARR:
+ case BITSET:
+ return new ReferenceFieldAccessor(field, colIdx, mode);
+
+ default:
+ assert false : "Invalid mode " + mode;
+ }
+
+ throw new IllegalArgumentException("Failed to create accessor for field [name=" + field.getName() + ']');
+ }
+ catch (NoSuchFieldException | SecurityException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ /**
+ * Create accessor for the field.
+ *
+ * @param col Column.
+ * @param colIdx Column index.
+ * @param mode Binary mode.
+ * @return Accessor.
+ */
+ static UnsafeFieldAccessor createIdentityAccessor(Column col, int colIdx, BinaryMode mode) {
+ switch (mode) {
+ // Marshaller read/write object contract methods allowed boxed types only.
+ case P_BYTE:
+ case P_SHORT:
+ case P_INT:
+ case P_LONG:
+ case P_FLOAT:
+ case P_DOUBLE:
+ throw new IllegalArgumentException("Primitive key/value types are not possible by API contract.");
+
+ case BYTE:
+ case SHORT:
+ case INT:
+ case LONG:
+ case FLOAT:
+ case DOUBLE:
+ case STRING:
+ case UUID:
+ case BYTE_ARR:
+ case BITSET:
+ return new IdentityAccessor(colIdx, mode);
+
+ default:
+ assert false : "Invalid mode " + mode;
+ }
+
+ throw new IllegalArgumentException("Failed to create accessor for column [name=" + col.name() + ']');
+ }
+
+ /**
+ * Protected constructor.
+ *
+ * @param field Field.
+ * @param colIdx Column index.
+ * @param mode Binary mode;
+ */
+ protected UnsafeFieldAccessor(Field field, int colIdx, BinaryMode mode) {
+ assert field != null;
+ assert colIdx >= 0;
+ assert mode != null;
+
+ this.colIdx = colIdx;
+ this.mode = mode;
+ offset = IgniteUnsafeUtils.objectFieldOffset(field);
+ name = field.getName();
+ }
+
+ /**
+ * Protected constructor.
+ *
+ * @param colIdx Column index.
+ * @param mode Binary mode;
+ */
+ private UnsafeFieldAccessor(int colIdx, BinaryMode mode) {
+ assert colIdx >= 0;
+ assert mode != null;
+
+ this.colIdx = colIdx;
+ this.mode = mode;
+ offset = 0;
+ name = null;
+ }
+
+ /**
+ * Get binary read/write mode.
+ *
+ * @return Binary mode.
+ */
+ public BinaryMode mode() {
+ return mode;
+ }
+
+ /**
+ * Write object field value to tuple.
+ *
+ * @param obj Source object.
+ * @param writer Tuple writer.
+ * @throws SerializationException If failed.
+ */
+ public void write(Object obj, TupleAssembler writer) throws SerializationException {
+ try {
+ write0(Objects.requireNonNull(obj), writer);
+ }
+ catch (Exception ex) {
+ if (includeSensitive() && name != null)
+ throw new SerializationException("Failed to read field [id=" + colIdx + ']', ex);
+ else
+ throw new SerializationException("Failed to write field [id=" + colIdx + ']', ex);
+ }
+ }
+
+ /**
+ * Write object field value to tuple.
+ *
+ * @param obj Source object.
+ * @param writer Tuple writer.
+ * @throws IllegalAccessException If failed.
+ */
+ protected abstract void write0(Object obj, TupleAssembler writer) throws IllegalAccessException;
+
+ /**
+ * Reads value fom tuple to object field.
+ *
+ * @param obj Target object.
+ * @param reader Tuple reader.
+ * @throws SerializationException If failed.
+ */
+ public void read(Object obj, Tuple reader) throws SerializationException {
+ try {
+ read0(Objects.requireNonNull(obj), reader);
+ }
+ catch (Exception ex) {
+ if (includeSensitive() && name != null)
+ throw new SerializationException("Failed to read field [name=" + name + ']', ex);
+ else
+ throw new SerializationException("Failed to read field [id=" + colIdx + ']', ex);
+ }
+ }
+
+ /**
+ * Reads value fom tuple to object field.
+ *
+ * @param obj Target object.
+ * @param reader Tuple reader.
+ * @throws IllegalAccessException If failed.
+ */
+ protected abstract void read0(Object obj, Tuple reader) throws IllegalAccessException;
+
+ /**
+ * Read value.
+ *
+ * @param reader Tuple reader.
+ * @return Object.
+ */
+ public Object read(Tuple reader) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Reads object field value.
+ *
+ * @param obj Object.
+ * @return Field value of given object.
+ */
+ @Nullable Object value(Object obj) {
+ return IgniteUnsafeUtils.getObjectField(Objects.requireNonNull(obj), offset);
+ }
+
+ /**
+ * Accessor for field of primitive {@code byte} type.
+ */
+ private static class IdentityAccessor extends UnsafeFieldAccessor {
+ /**
+ * Constructor.
+ *
+ * @param colIdx Column index.
+ * @param mode Binary mode.
+ */
+ public IdentityAccessor(int colIdx, BinaryMode mode) {
+ super(colIdx, mode);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void write0(Object obj, TupleAssembler writer) {
+ JavaSerializer.writeRefObject(Objects.requireNonNull(obj, "Null values are not supported."), writer, mode);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void read0(Object obj, Tuple reader) {
+ throw new UnsupportedOperationException("Called identity accessor for object field.");
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object read(Tuple reader) {
+ return JavaSerializer.readRefValue(reader, colIdx, mode);
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable Object value(Object obj) {
+ return obj;
+ }
+ }
+
+ /**
+ * Accessor for field of primitive {@code byte} type.
+ */
+ private static class BytePrimitiveAccessor extends UnsafeFieldAccessor {
+ /**
+ * Constructor.
+ *
+ * @param field Field.
+ * @param colIdx Column index.
+ */
+ public BytePrimitiveAccessor(Field field, int colIdx) {
+ super(field, colIdx, BinaryMode.P_BYTE);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void write0(Object obj, TupleAssembler writer) {
+ final byte val = IgniteUnsafeUtils.getByteField(obj, offset);
+
+ writer.appendByte(val);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void read0(Object obj, Tuple reader) {
+ final byte val = reader.byteValue(colIdx);
+
+ IgniteUnsafeUtils.putByteField(obj, offset, val);
+ }
+ }
+
+ /**
+ * Accessor for field of primitive {@code short} type.
+ */
+ private static class ShortPrimitiveAccessor extends UnsafeFieldAccessor {
+ /**
+ * Constructor.
+ *
+ * @param field Field.
+ * @param colIdx Column index.
+ */
+ public ShortPrimitiveAccessor(Field field, int colIdx) {
+ super(field, colIdx, BinaryMode.P_SHORT);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void write0(Object obj, TupleAssembler writer) {
+ final short val = IgniteUnsafeUtils.getShortField(obj, offset);
+
+ writer.appendShort(val);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void read0(Object obj, Tuple reader) {
+ final short val = reader.shortValue(colIdx);
+
+ IgniteUnsafeUtils.putShortField(obj, offset, val);
+ }
+ }
+
+ /**
+ * Accessor for field of primitive {@code int} type.
+ */
+ private static class IntPrimitiveAccessor extends UnsafeFieldAccessor {
+ /**
+ * Constructor.
+ *
+ * @param field Field.
+ * @param colIdx Column index.
+ */
+ public IntPrimitiveAccessor(Field field, int colIdx) {
+ super(field, colIdx, BinaryMode.P_INT);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void write0(Object obj, TupleAssembler writer) {
+ final int val = IgniteUnsafeUtils.getIntField(obj, offset);
+
+ writer.appendInt(val);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void read0(Object obj, Tuple reader) {
+ final int val = reader.intValue(colIdx);
+
+ IgniteUnsafeUtils.putIntField(obj, offset, val);
+ }
+ }
+
+ /**
+ * Accessor for field of primitive {@code long} type.
+ */
+ private static class LongPrimitiveAccessor extends UnsafeFieldAccessor {
+ /**
+ * Constructor.
+ *
+ * @param field Field.
+ * @param colIdx Column index.
+ */
+ public LongPrimitiveAccessor(Field field, int colIdx) {
+ super(field, colIdx, BinaryMode.P_LONG);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void write0(Object obj, TupleAssembler writer) {
+ final long val = IgniteUnsafeUtils.getLongField(obj, offset);
+
+ writer.appendLong(val);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void read0(Object obj, Tuple reader) {
+ final long val = reader.longValue(colIdx);
+
+ IgniteUnsafeUtils.putLongField(obj, offset, val);
+ }
+ }
+
+ /**
+ * Accessor for field of primitive {@code float} type.
+ */
+ private static class FloatPrimitiveAccessor extends UnsafeFieldAccessor {
+ /**
+ * Constructor.
+ *
+ * @param field Field.
+ * @param colIdx Column index.
+ */
+ public FloatPrimitiveAccessor(Field field, int colIdx) {
+ super(field, colIdx, BinaryMode.P_FLOAT);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void write0(Object obj, TupleAssembler writer) {
+ final float val = IgniteUnsafeUtils.getFloatField(obj, offset);
+
+ writer.appendFloat(val);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void read0(Object obj, Tuple reader) {
+ final float val = reader.floatValue(colIdx);
+
+ IgniteUnsafeUtils.putFloatField(obj, offset, val);
+ }
+ }
+
+ /**
+ * Accessor for field of primitive {@code double} type.
+ */
+ private static class DoublePrimitiveAccessor extends UnsafeFieldAccessor {
+ /**
+ * Constructor.
+ *
+ * @param field Field.
+ * @param colIdx Column index.
+ */
+ public DoublePrimitiveAccessor(Field field, int colIdx) {
+ super(field, colIdx, BinaryMode.P_DOUBLE);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void write0(Object obj, TupleAssembler writer) {
+ final double val = IgniteUnsafeUtils.getDoubleField(obj, offset);
+
+ writer.appendDouble(val);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void read0(Object obj, Tuple reader) {
+ final double val = reader.doubleValue(colIdx);
+
+ IgniteUnsafeUtils.putDoubleField(obj, offset, val);
+ }
+ }
+
+ /**
+ * Accessor for field of reference type.
+ */
+ private static class ReferenceFieldAccessor extends UnsafeFieldAccessor {
+ /**
+ * Constructor.
+ *
+ * @param field Field.
+ * @param colIdx Column index.
+ * @param mode Binary mode.
+ */
+ ReferenceFieldAccessor(Field field, int colIdx, BinaryMode mode) {
+ super(field, colIdx, mode);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void write0(Object obj, TupleAssembler writer) {
+ assert obj != null;
+ assert writer != null;
+
+ Object val;
+
+ val = IgniteUnsafeUtils.getObjectField(obj, offset);
+
+ if (val == null) {
+ writer.appendNull();
+
+ return;
+ }
+
+ JavaSerializer.writeRefObject(val, writer, mode);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void read0(Object obj, Tuple reader) {
+ Object val = JavaSerializer.readRefValue(reader, colIdx, mode);
+
+ IgniteUnsafeUtils.putObjectField(obj, offset, val);
+ }
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/Factory.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/Factory.java
new file mode 100644
index 0000000..663b739
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/Factory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.util;
+
+/**
+ * Factory interface.
+ *
+ * @param <T> Object type.
+ */
+public interface Factory<T> {
+ /**
+ * Creates object.
+ *
+ * @return Object.
+ */
+ public T create();
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/IgniteUnsafeUtils.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/IgniteUnsafeUtils.java
new file mode 100644
index 0000000..74ea3a3
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/IgniteUnsafeUtils.java
@@ -0,0 +1,273 @@
+/*
+ * 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.util;
+
+import java.lang.reflect.Field;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import sun.misc.Unsafe;
+
+/**
+ * Unsafe utility.
+ */
+//TODO Move class to 'java-8' profile. Java9+ should use varhandles instead.
+public final class IgniteUnsafeUtils {
+ /** Unsafe. */
+ private static final Unsafe UNSAFE = unsafe();
+
+ /**
+ * @return Instance of Unsafe class.
+ */
+ private static Unsafe unsafe() {
+ try {
+ return Unsafe.getUnsafe();
+ }
+ catch (SecurityException ignored) {
+ try {
+ return AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Unsafe>() {
+ @Override public Unsafe run() throws Exception {
+ Field f = Unsafe.class.getDeclaredField("theUnsafe");
+
+ f.setAccessible(true);
+
+ return (Unsafe)f.get(null);
+ }
+ });
+ }
+ catch (PrivilegedActionException e) {
+ throw new RuntimeException("Could not initialize intrinsics.", e.getCause());
+ }
+ }
+ }
+
+ /**
+ * Returns object field offset.
+ *
+ * @param field Field.
+ * @return Object field offset.
+ */
+ public static long objectFieldOffset(Field field) {
+ return UNSAFE.objectFieldOffset(field);
+ }
+
+ /**
+ * Gets boolean value from object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @return Boolean value from object field.
+ */
+ public static boolean getBooleanField(Object obj, long fieldOff) {
+ return UNSAFE.getBoolean(obj, fieldOff);
+ }
+
+ /**
+ * Stores boolean value into object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @param val Value.
+ */
+ public static void putBooleanField(Object obj, long fieldOff, boolean val) {
+ UNSAFE.putBoolean(obj, fieldOff, val);
+ }
+
+ /**
+ * Gets byte value from object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @return Byte value from object field.
+ */
+ public static byte getByteField(Object obj, long fieldOff) {
+ return UNSAFE.getByte(obj, fieldOff);
+ }
+
+ /**
+ * Stores byte value into object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @param val Value.
+ */
+ public static void putByteField(Object obj, long fieldOff, byte val) {
+ UNSAFE.putByte(obj, fieldOff, val);
+ }
+
+ /**
+ * Gets short value from object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @return Short value from object field.
+ */
+ public static short getShortField(Object obj, long fieldOff) {
+ return UNSAFE.getShort(obj, fieldOff);
+ }
+
+ /**
+ * Stores short value into object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @param val Value.
+ */
+ public static void putShortField(Object obj, long fieldOff, short val) {
+ UNSAFE.putShort(obj, fieldOff, val);
+ }
+
+ /**
+ * Gets char value from object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @return Char value from object field.
+ */
+ public static char getCharField(Object obj, long fieldOff) {
+ return UNSAFE.getChar(obj, fieldOff);
+ }
+
+ /**
+ * Stores char value into object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @param val Value.
+ */
+ public static void putCharField(Object obj, long fieldOff, char val) {
+ UNSAFE.putChar(obj, fieldOff, val);
+ }
+
+ /**
+ * Gets integer value from object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @return Integer value from object field.
+ */
+ public static int getIntField(Object obj, long fieldOff) {
+ return UNSAFE.getInt(obj, fieldOff);
+ }
+
+ /**
+ * Stores integer value into object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @param val Value.
+ */
+ public static void putIntField(Object obj, long fieldOff, int val) {
+ UNSAFE.putInt(obj, fieldOff, val);
+ }
+
+ /**
+ * Gets long value from object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @return Long value from object field.
+ */
+ public static long getLongField(Object obj, long fieldOff) {
+ return UNSAFE.getLong(obj, fieldOff);
+ }
+
+ /**
+ * Stores long value into object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @param val Value.
+ */
+ public static void putLongField(Object obj, long fieldOff, long val) {
+ UNSAFE.putLong(obj, fieldOff, val);
+ }
+
+ /**
+ * Gets float value from object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @return Float value from object field.
+ */
+ public static float getFloatField(Object obj, long fieldOff) {
+ return UNSAFE.getFloat(obj, fieldOff);
+ }
+
+ /**
+ * Stores float value into object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @param val Value.
+ */
+ public static void putFloatField(Object obj, long fieldOff, float val) {
+ UNSAFE.putFloat(obj, fieldOff, val);
+ }
+
+ /**
+ * Gets double value from object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @return Double value from object field.
+ */
+ public static double getDoubleField(Object obj, long fieldOff) {
+ return UNSAFE.getDouble(obj, fieldOff);
+ }
+
+ /**
+ * Stores double value into object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @param val Value.
+ */
+ public static void putDoubleField(Object obj, long fieldOff, double val) {
+ UNSAFE.putDouble(obj, fieldOff, val);
+ }
+
+ /**
+ * Gets reference from object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @return Reference from object field.
+ */
+ public static Object getObjectField(Object obj, long fieldOff) {
+ return UNSAFE.getObject(obj, fieldOff);
+ }
+
+ /**
+ * Stores reference value into object field.
+ *
+ * @param obj Object.
+ * @param fieldOff Field offset.
+ * @param val Value.
+ */
+ public static void putObjectField(Object obj, long fieldOff, Object val) {
+ UNSAFE.putObject(obj, fieldOff, val);
+ }
+
+ /**
+ * Stub.
+ */
+ private IgniteUnsafeUtils() {
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/ObjectFactory.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/ObjectFactory.java
new file mode 100644
index 0000000..f31965f
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/ObjectFactory.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.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Object factory.
+ */
+public class ObjectFactory<T> implements Factory<T> {
+ /** Class default constructor. */
+ private final Constructor<T> cnstr;
+
+ /**
+ * Constructor.
+ *
+ * @param tClass Class.
+ */
+ public ObjectFactory(Class<T> tClass) {
+ try {
+ cnstr = tClass.getDeclaredConstructor();
+
+ cnstr.setAccessible(true);
+ }
+ catch (NoSuchMethodException e) {
+ throw new IllegalStateException("Class has no default constructor: class=" + tClass.getName(), e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public T create() throws IllegalStateException {
+ try {
+ return cnstr.newInstance();
+ }
+ catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
+ throw new IllegalStateException("Failed to instantiate class: " + cnstr.getDeclaringClass().getName(), e);
+ }
+ }
+
+ /**
+ * @return Class of object created by the factory.
+ */
+ public Class<T> getClazz() {
+ return cnstr.getDeclaringClass();
+ }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/Pair.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/Pair.java
new file mode 100644
index 0000000..a88fc7d
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/Pair.java
@@ -0,0 +1,57 @@
+/*
+ * 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.util;
+
+/**
+ * Pair of objects.
+ *
+ * @param <T> First object.
+ * @param <V> Second object.
+ */
+public class Pair<T, V> {
+ /** Fisrt obj. */
+ private final T first;
+
+ /** Second obj. */
+ private final V second;
+
+ /**
+ * Constructor.
+ *
+ * @param first First object.
+ * @param second Second object.
+ */
+ public Pair(T first, V second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ /**
+ * @return First object.
+ */
+ public T getFirst() {
+ return first;
+ }
+
+ /**
+ * @return Second object.
+ */
+ public V getSecond() {
+ return second;
+ }
+}
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/benchmarks/SerializerBenchmarkTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/benchmarks/SerializerBenchmarkTest.java
new file mode 100644
index 0000000..23193ba
--- /dev/null
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/benchmarks/SerializerBenchmarkTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.benchmarks;
+
+import java.lang.reflect.Field;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+import org.apache.ignite.internal.schema.marshaller.SerializerFactory;
+import org.apache.ignite.internal.util.Factory;
+import org.apache.ignite.internal.util.ObjectFactory;
+import org.codehaus.commons.compiler.CompilerFactoryFactory;
+import org.codehaus.commons.compiler.IClassBodyEvaluator;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import static org.apache.ignite.internal.schema.NativeType.LONG;
+
+/**
+ * Serializer benchmark.
+ */
+@State(Scope.Benchmark)
+@Warmup(time = 10, iterations = 3, timeUnit = TimeUnit.SECONDS)
+@Measurement(time = 10, iterations = 5, timeUnit = TimeUnit.SECONDS)
+@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@Fork(1)
+public class SerializerBenchmarkTest {
+ /** Random. */
+ private Random rnd = new Random();
+
+ /** Reflection-based Serializer. */
+ private Serializer serializer;
+
+ /** Test object factory. */
+ private Factory<?> objectFactory;
+
+ /** Object fields count. */
+ @Param({"10", "100"})
+ public int fieldsCount;
+
+ /** Serializer. */
+ @Param({"Janino", "Java"})
+ public String serializerName;
+
+ /**
+ * Runner.
+ */
+ public static void main(String[] args) throws RunnerException {
+ Options opt = new OptionsBuilder()
+ .include(SerializerBenchmarkTest.class.getSimpleName())
+ .build();
+
+ new Runner(opt).run();
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Setup
+ public void init() throws Exception {
+ long seed = System.currentTimeMillis();
+
+ rnd = new Random(seed);
+
+ final Class<?> valClass = createGeneratedObjectClass(fieldsCount, Long.TYPE);
+ objectFactory = new ObjectFactory<>(valClass);
+
+ Columns keyCols = new Columns(new Column("key", LONG, true));
+ Columns valCols = mapFieldsToColumns(valClass);
+ final SchemaDescriptor schema = new SchemaDescriptor(1, keyCols, valCols);
+
+ if ("Java".equals(serializerName))
+ serializer = SerializerFactory.createJavaSerializerFactory().create(schema, Long.class, valClass);
+ else
+ serializer = SerializerFactory.createJaninoSerializerFactory().create(schema, Long.class, valClass);
+ }
+
+ /**
+ * Measure serialization-deserialization operation cost.
+ *
+ * @param bh Black hole.
+ * @throws Exception If failed.
+ */
+ @Benchmark
+ public void measureSerializeDeserializeCost(Blackhole bh) throws Exception {
+ Long key = rnd.nextLong();
+
+ Object val = objectFactory.create();
+ byte[] bytes = serializer.serialize(key, val);
+
+ Object restoredKey = serializer.deserializeKey(bytes);
+ Object restoredVal = serializer.deserializeValue(bytes);
+
+ bh.consume(restoredVal);
+ bh.consume(restoredKey);
+ }
+
+ /**
+ * Map fields to columns.
+ *
+ * @param aClass Object class.
+ * @return Columns for schema
+ */
+ private Columns mapFieldsToColumns(Class<?> aClass) {
+ final Field[] fields = aClass.getDeclaredFields();
+ final Column[] cols = new Column[fields.length];
+
+ for (int i = 0; i < fields.length; i++) {
+ assert fields[i].getType() == Long.TYPE : "Only 'long' field type is supported.";
+
+ cols[i] = new Column("col" + i, LONG, false);
+ }
+
+ return new Columns(cols);
+ }
+
+ /**
+ * Generate class for test objects.
+ *
+ * @param maxFields Max class member fields.
+ * @param fieldType Field type.
+ * @return Generated test object class.
+ * @throws Exception If failed.
+ */
+ private Class<?> createGeneratedObjectClass(int maxFields, Class<?> fieldType) throws Exception {
+ final IClassBodyEvaluator ce = CompilerFactoryFactory.getDefaultCompilerFactory().newClassBodyEvaluator();
+
+ ce.setClassName("TestObject");
+ ce.setDefaultImports("java.util.Random");
+
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < maxFields; i++)
+ sb.append(fieldType.getName()).append(" col").append(i).append(";\n");
+
+ // Constructor.
+ sb.append("public TestObject() {\n");
+ sb.append(" Random rnd = new Random();\n");
+ for (int i = 0; i < maxFields; i++)
+ sb.append(" col").append(i).append(" = rnd.nextLong();\n");
+ sb.append("}\n");
+
+ try {
+ ce.setParentClassLoader(getClass().getClassLoader());
+ ce.cook(sb.toString());
+
+ return ce.getClazz();
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException("Failed to compile/instantiate generated Serializer.", ex);
+ }
+ }
+}
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/schema/TestUtils.java b/modules/commons/src/test/java/org/apache/ignite/internal/schema/TestUtils.java
new file mode 100644
index 0000000..4ed5fec
--- /dev/null
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/schema/TestUtils.java
@@ -0,0 +1,121 @@
+/*
+ * 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.schema;
+
+import java.util.BitSet;
+import java.util.Random;
+
+/**
+ * Test utility class.
+ */
+public class TestUtils {
+ /**
+ * Generates randon value of given type.
+ *
+ * @param rnd Random generator.
+ * @param type Type.
+ * @return Random object of asked type.
+ */
+ public static Object generateRandomValue(Random rnd, NativeType type) {
+ switch (type.spec()) {
+ case BYTE:
+ return (byte)rnd.nextInt(255);
+
+ case SHORT:
+ return (short)rnd.nextInt(65535);
+
+ case INTEGER:
+ return rnd.nextInt();
+
+ case LONG:
+ return rnd.nextLong();
+
+ case FLOAT:
+ return rnd.nextFloat();
+
+ case DOUBLE:
+ return rnd.nextDouble();
+
+ case UUID:
+ return new java.util.UUID(rnd.nextLong(), rnd.nextLong());
+
+ case STRING:
+ return randomString(rnd, rnd.nextInt(255));
+
+ case BYTES:
+ return randomBytes(rnd, rnd.nextInt(255));
+
+ case BITMASK: {
+ Bitmask maskType = (Bitmask)type;
+
+ return randomBitSet(rnd, maskType.bits());
+ }
+
+ default:
+ throw new IllegalStateException("Unsupported type: " + type);
+ }
+ }
+
+ /**
+ * @param rnd Random generator.
+ * @param bits Amount of bits in bitset.
+ * @return Random BitSet.
+ */
+ public static BitSet randomBitSet(Random rnd, int bits) {
+ BitSet set = new BitSet();
+
+ for (int i = 0; i < bits; i++) {
+ if (rnd.nextBoolean())
+ set.set(i);
+ }
+
+ return set;
+ }
+
+ /**
+ * @param rnd Random generator.
+ * @param len Byte array length.
+ * @return Radmon byte array.
+ */
+ public static byte[] randomBytes(Random rnd, int len) {
+ byte[] data = new byte[len];
+ rnd.nextBytes(data);
+
+ return data;
+ }
+
+ /**
+ * @param rnd Random generator.
+ * @param len String length.
+ * @return Randon string.
+ */
+ public static String randomString(Random rnd, int len) {
+ StringBuilder sb = new StringBuilder();
+
+ while (sb.length() < len) {
+ char pt = (char)rnd.nextInt(Character.MAX_VALUE + 1);
+
+ if (Character.isDefined(pt) &&
+ Character.getType(pt) != Character.PRIVATE_USE &&
+ !Character.isSurrogate(pt))
+ sb.append(pt);
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/schema/TupleTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/schema/TupleTest.java
index 746e4d5..2b8efee 100644
--- a/modules/commons/src/test/java/org/apache/ignite/internal/schema/TupleTest.java
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/schema/TupleTest.java
@@ -191,69 +191,7 @@ public class TupleTest {
*
*/
private Object generateRandomValue(NativeType type) {
- switch (type.spec()) {
- case BYTE:
- return (byte)rnd.nextInt(255);
-
- case SHORT:
- return (short)rnd.nextInt(65535);
-
- case INTEGER:
- return rnd.nextInt();
-
- case LONG:
- return rnd.nextLong();
-
- case FLOAT:
- return rnd.nextFloat();
-
- case DOUBLE:
- return rnd.nextDouble();
-
- case UUID:
- return new java.util.UUID(rnd.nextLong(), rnd.nextLong());
-
- case STRING: {
- int size = rnd.nextInt(255);
-
- StringBuilder sb = new StringBuilder();
-
- while (sb.length() < size) {
- char pt = (char)rnd.nextInt(Character.MAX_VALUE + 1);
-
- if (Character.isDefined(pt) &&
- Character.getType(pt) != Character.PRIVATE_USE &&
- !Character.isSurrogate(pt))
- sb.append(pt);
- }
-
- return sb.toString();
- }
-
- case BYTES: {
- int size = rnd.nextInt(255);
- byte[] data = new byte[size];
- rnd.nextBytes(data);
-
- return data;
- }
-
- case BITMASK: {
- Bitmask maskType = (Bitmask)type;
-
- BitSet set = new BitSet();
-
- for (int i = 0; i < maskType.bits(); i++) {
- if (rnd.nextBoolean())
- set.set(i);
- }
-
- return set;
- }
-
- default:
- throw new IllegalStateException("Unsupported type: " + type);
- }
+ return TestUtils.generateRandomValue(rnd, type);
}
/**
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/JavaSerializerTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/JavaSerializerTest.java
new file mode 100644
index 0000000..49ef554
--- /dev/null
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/JavaSerializerTest.java
@@ -0,0 +1,494 @@
+/*
+ * 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.schema.marshaller;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Stream;
+import org.apache.ignite.internal.schema.Bitmask;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.NativeTypeSpec;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.TestUtils;
+import org.apache.ignite.internal.schema.marshaller.generator.JaninoSerializerGenerator;
+import org.apache.ignite.internal.schema.marshaller.reflection.JavaSerializerFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DynamicNode;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.apache.ignite.internal.schema.NativeType.BYTE;
+import static org.apache.ignite.internal.schema.NativeType.BYTES;
+import static org.apache.ignite.internal.schema.NativeType.DOUBLE;
+import static org.apache.ignite.internal.schema.NativeType.FLOAT;
+import static org.apache.ignite.internal.schema.NativeType.INTEGER;
+import static org.apache.ignite.internal.schema.NativeType.LONG;
+import static org.apache.ignite.internal.schema.NativeType.SHORT;
+import static org.apache.ignite.internal.schema.NativeType.STRING;
+import static org.apache.ignite.internal.schema.NativeType.UUID;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+/**
+ * Serializer test.
+ */
+public class JavaSerializerTest {
+ /**
+ * @return List of serializers for test.
+ */
+ private static List<SerializerFactory> serializerFactoryProvider() {
+ return Arrays.asList(
+ new JaninoSerializerGenerator(),
+ new JavaSerializerFactory()
+ );
+ }
+
+ /** Random. */
+ private Random rnd;
+
+ /**
+ *
+ */
+ @BeforeEach
+ public void initRandom() {
+ long seed = System.currentTimeMillis();
+
+ System.out.println("Using seed: " + seed + "L;");
+
+ rnd = new Random(seed);
+ }
+
+ /**
+ *
+ */
+ @TestFactory
+ public Stream<DynamicNode> testBasicTypes() {
+ NativeType[] types = new NativeType[] {BYTE, SHORT, INTEGER, LONG, FLOAT, DOUBLE, UUID, STRING, BYTES, Bitmask.of(5)};
+
+ return serializerFactoryProvider().stream().map(factory ->
+ dynamicContainer(
+ factory.getClass().getSimpleName(),
+ Stream.concat(
+ // Test pure types.
+ Stream.of(types).map(type ->
+ dynamicTest("testBasicTypes(" + type.spec().name() + ')', () -> checkBasicType(factory, type, type))
+ ),
+
+ // Test pairs of mixed types.
+ Stream.of(
+ dynamicTest("testMixTypes 1", () -> checkBasicType(factory, LONG, INTEGER)),
+ dynamicTest("testMixTypes 1", () -> checkBasicType(factory, FLOAT, DOUBLE)),
+ dynamicTest("testMixTypes 1", () -> checkBasicType(factory, INTEGER, BYTES)),
+ dynamicTest("testMixTypes 1", () -> checkBasicType(factory, STRING, LONG)),
+ dynamicTest("testMixTypes 1", () -> checkBasicType(factory, Bitmask.of(9), BYTES))
+ )
+ )
+ ));
+ }
+
+ /**
+ * @throws SerializationException If serialization failed.
+ */
+ @ParameterizedTest
+ @MethodSource("serializerFactoryProvider")
+ public void testComplexType(SerializerFactory factory) throws SerializationException {
+ Column[] cols = new Column[] {
+ new Column("pByteCol", BYTE, false),
+ new Column("pShortCol", SHORT, false),
+ new Column("pIntCol", INTEGER, false),
+ new Column("pLongCol", LONG, false),
+ new Column("pFloatCol", FLOAT, false),
+ new Column("pDoubleCol", DOUBLE, false),
+
+ new Column("byteCol", BYTE, true),
+ new Column("shortCol", SHORT, true),
+ new Column("intCol", INTEGER, true),
+ new Column("longCol", LONG, true),
+ new Column("nullLongCol", LONG, true),
+ new Column("floatCol", FLOAT, true),
+ new Column("doubleCol", DOUBLE, true),
+
+ new Column("uuidCol", UUID, true),
+ new Column("bitmaskCol", Bitmask.of(42), true),
+ new Column("stringCol", STRING, true),
+ new Column("nullBytesCol", BYTES, true),
+ new Column("bytesCol", BYTES, true),
+ };
+
+ SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(cols), new Columns(cols.clone()));
+
+ final Object key = TestObject.randomObject(rnd);
+ final Object val = TestObject.randomObject(rnd);
+
+ Serializer serializer = factory.create(schema, key.getClass(), val.getClass());
+
+ byte[] bytes = serializer.serialize(key, val);
+
+ // Try different order.
+ Object restoredVal = serializer.deserializeValue(bytes);
+ Object restoredKey = serializer.deserializeKey(bytes);
+
+ assertTrue(key.getClass().isInstance(restoredKey));
+ assertTrue(val.getClass().isInstance(restoredVal));
+
+ assertEquals(key, restoredKey);
+ assertEquals(val, restoredVal);
+ }
+
+ /**
+ *
+ */
+ @ParameterizedTest
+ @MethodSource("serializerFactoryProvider")
+ public void testClassWithIncorrectBitmaskSize(SerializerFactory factory) {
+ Column[] cols = new Column[] {
+ new Column("pLongCol", LONG, false),
+ new Column("bitmaskCol", Bitmask.of(9), true),
+ };
+
+ SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(cols), new Columns(cols.clone()));
+
+ final Object key = TestObject.randomObject(rnd);
+ final Object val = TestObject.randomObject(rnd);
+
+ Serializer serializer = factory.create(schema, key.getClass(), val.getClass());
+
+ assertThrows(
+ SerializationException.class,
+ () -> serializer.serialize(key, val),
+ "Failed to write field [name=bitmaskCol]"
+ );
+ }
+
+ /**
+ *
+ */
+ @ParameterizedTest
+ @MethodSource("serializerFactoryProvider")
+ public void testClassWithWrongFieldType(SerializerFactory factory) {
+ Column[] cols = new Column[] {
+ new Column("bitmaskCol", Bitmask.of(42), true),
+ new Column("shortCol", UUID, true)
+ };
+
+ SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(cols), new Columns(cols.clone()));
+
+ final Object key = TestObject.randomObject(rnd);
+ final Object val = TestObject.randomObject(rnd);
+
+ Serializer serializer = factory.create(schema, key.getClass(), val.getClass());
+
+ assertThrows(
+ SerializationException.class,
+ () -> serializer.serialize(key, val),
+ "Failed to write field [name=shortCol]"
+ );
+ }
+
+ /**
+ *
+ */
+ @ParameterizedTest
+ @MethodSource("serializerFactoryProvider")
+ public void testClassWithPrivateConstructor(SerializerFactory factory) throws SerializationException {
+ Column[] cols = new Column[] {
+ new Column("pLongCol", LONG, false),
+ };
+
+ SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(cols), new Columns(cols.clone()));
+
+ final Object key = PrivateTestObject.randomObject(rnd);
+ final Object val = PrivateTestObject.randomObject(rnd);
+
+ Serializer serializer = factory.create(schema, key.getClass(), val.getClass());
+
+ byte[] bytes = serializer.serialize(key, val);
+
+ Object key1 = serializer.deserializeKey(bytes);
+ Object val1 = serializer.deserializeValue(bytes);
+
+ assertTrue(key.getClass().isInstance(key1));
+ assertTrue(val.getClass().isInstance(val1));
+
+ assertEquals(key, key);
+ assertEquals(val, val1);
+ }
+
+ /**
+ *
+ */
+ @ParameterizedTest
+ @MethodSource("serializerFactoryProvider")
+ public void testClassWithNoDefaultConstructor(SerializerFactory factory) throws SerializationException {
+ Column[] cols = new Column[] {
+ new Column("pLongCol", LONG, false),
+ };
+
+ SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(cols), new Columns(cols.clone()));
+
+ final Object key = WrongTestObject.randomObject(rnd);
+ final Object val = WrongTestObject.randomObject(rnd);
+
+ assertThrows(IllegalStateException.class,
+ () -> factory.create(schema, key.getClass(), val.getClass()),
+ "Class has no default constructor: class=org.apache.ignite.internal.schema.marshaller.JavaSerializerTest$WrongTestObject"
+ );
+ }
+
+ /**
+ * Generate random key-value pair of given types and
+ * check serialization and deserialization works fine.
+ *
+ * @param factory Serializer factory.
+ * @param keyType Key type.
+ * @param valType Value type.
+ * @throws SerializationException If (de)serialization failed.
+ */
+ private void checkBasicType(SerializerFactory factory, NativeType keyType,
+ NativeType valType) throws SerializationException {
+ final Object key = generateRandomValue(keyType);
+ final Object val = generateRandomValue(valType);
+
+ Column[] keyCols = new Column[] {new Column("key", keyType, false)};
+ Column[] valCols = new Column[] {new Column("val", valType, false)};
+
+ SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(keyCols), new Columns(valCols));
+
+ Serializer serializer = factory.create(schema, key.getClass(), val.getClass());
+
+ byte[] bytes = serializer.serialize(key, val);
+
+ Object key1 = serializer.deserializeKey(bytes);
+ Object val1 = serializer.deserializeValue(bytes);
+
+ assertTrue(key.getClass().isInstance(key1));
+ assertTrue(val.getClass().isInstance(val1));
+
+ compareObjects(keyType, key, key);
+ compareObjects(valType, val, val1);
+ }
+
+ /**
+ * Compare object regarding NativeType.
+ *
+ * @param type Native type.
+ * @param exp Expected value.
+ * @param act Actual value.
+ */
+ private void compareObjects(NativeType type, Object exp, Object act) {
+ if (type.spec() == NativeTypeSpec.BYTES)
+ assertArrayEquals((byte[])exp, (byte[])act);
+ else
+ assertEquals(exp, act);
+ }
+
+ /**
+ * Generates randon value of given type.
+ *
+ * @param type Type.
+ */
+ private Object generateRandomValue(NativeType type) {
+ return TestUtils.generateRandomValue(rnd, type);
+ }
+
+ /**
+ * Test object.
+ */
+ public static class TestObject {
+ /**
+ * @return Random TestObject.
+ */
+ public static TestObject randomObject(Random rnd) {
+ final TestObject obj = new TestObject();
+
+ obj.pByteCol = (byte)rnd.nextInt(255);
+ obj.pShortCol = (short)rnd.nextInt(65535);
+ obj.pIntCol = rnd.nextInt();
+ obj.pLongCol = rnd.nextLong();
+ obj.pFloatCol = rnd.nextFloat();
+ obj.pDoubleCol = rnd.nextDouble();
+
+ obj.byteCol = (byte)rnd.nextInt(255);
+ obj.shortCol = (short)rnd.nextInt(65535);
+ obj.intCol = rnd.nextInt();
+ obj.longCol = rnd.nextLong();
+ obj.floatCol = rnd.nextFloat();
+ obj.doubleCol = rnd.nextDouble();
+
+ obj.uuidCol = new UUID(rnd.nextLong(), rnd.nextLong());
+ obj.bitmaskCol = TestUtils.randomBitSet(rnd, 42);
+ obj.stringCol = TestUtils.randomString(rnd, rnd.nextInt(255));
+ obj.bytesCol = TestUtils.randomBytes(rnd, rnd.nextInt(255));
+
+ return obj;
+ }
+
+ // Primitive typed
+ private byte pByteCol;
+ private short pShortCol;
+ private int pIntCol;
+ private long pLongCol;
+ private float pFloatCol;
+ private double pDoubleCol;
+
+ // Reference typed
+ private Byte byteCol;
+ private Short shortCol;
+ private Integer intCol;
+ private Long longCol;
+ private Long nullLongCol;
+ private Float floatCol;
+ private Double doubleCol;
+
+ private UUID uuidCol;
+ private BitSet bitmaskCol;
+ private String stringCol;
+ private byte[] bytesCol;
+ private byte[] nullBytesCol;
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ TestObject object = (TestObject)o;
+
+ return pByteCol == object.pByteCol &&
+ pShortCol == object.pShortCol &&
+ pIntCol == object.pIntCol &&
+ pLongCol == object.pLongCol &&
+ Float.compare(object.pFloatCol, pFloatCol) == 0 &&
+ Double.compare(object.pDoubleCol, pDoubleCol) == 0 &&
+ Objects.equals(byteCol, object.byteCol) &&
+ Objects.equals(shortCol, object.shortCol) &&
+ Objects.equals(intCol, object.intCol) &&
+ Objects.equals(longCol, object.longCol) &&
+ Objects.equals(nullLongCol, object.nullLongCol) &&
+ Objects.equals(floatCol, object.floatCol) &&
+ Objects.equals(doubleCol, object.doubleCol) &&
+ Objects.equals(uuidCol, object.uuidCol) &&
+ Objects.equals(bitmaskCol, object.bitmaskCol) &&
+ Objects.equals(stringCol, object.stringCol) &&
+ Arrays.equals(bytesCol, object.bytesCol) &&
+ Arrays.equals(nullBytesCol, object.nullBytesCol);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return 73;
+ }
+ }
+
+ /**
+ * Test object with private constructor.
+ */
+ private static class PrivateTestObject {
+ /**
+ * @return Random TestObject.
+ */
+ static PrivateTestObject randomObject(Random rnd) {
+ final PrivateTestObject obj = new PrivateTestObject();
+
+ obj.pLongCol = rnd.nextLong();
+
+ return obj;
+ }
+
+ /** Value. */
+ private long pLongCol;
+
+ /**
+ * Private constructor.
+ */
+ @SuppressWarnings("RedundantNoArgConstructor")
+ private PrivateTestObject() {
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ PrivateTestObject object = (PrivateTestObject)o;
+
+ return pLongCol == object.pLongCol;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(pLongCol);
+ }
+ }
+
+ /**
+ * Test object without default constructor.
+ */
+ private static class WrongTestObject {
+ /**
+ * @return Random TestObject.
+ */
+ static WrongTestObject randomObject(Random rnd) {
+ return new WrongTestObject(rnd.nextLong());
+ }
+
+ /** Value. */
+ private final long pLongCol;
+
+ /**
+ * Private constructor.
+ */
+ private WrongTestObject(long val) {
+ pLongCol = val;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ WrongTestObject object = (WrongTestObject)o;
+
+ return pLongCol == object.pLongCol;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(pLongCol);
+ }
+ }
+}
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessorTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessorTest.java
new file mode 100644
index 0000000..8e864d6
--- /dev/null
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessorTest.java
@@ -0,0 +1,396 @@
+/*
+ * 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.schema.marshaller.reflection;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Objects;
+import java.util.Random;
+import java.util.UUID;
+import org.apache.ignite.internal.schema.Bitmask;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.TestUtils;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.SerializationException;
+import org.apache.ignite.internal.util.Pair;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import static org.apache.ignite.internal.schema.NativeType.BYTE;
+import static org.apache.ignite.internal.schema.NativeType.BYTES;
+import static org.apache.ignite.internal.schema.NativeType.DOUBLE;
+import static org.apache.ignite.internal.schema.NativeType.FLOAT;
+import static org.apache.ignite.internal.schema.NativeType.INTEGER;
+import static org.apache.ignite.internal.schema.NativeType.LONG;
+import static org.apache.ignite.internal.schema.NativeType.SHORT;
+import static org.apache.ignite.internal.schema.NativeType.STRING;
+import static org.apache.ignite.internal.schema.NativeType.UUID;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Check field accessor correctness.
+ */
+public class FieldAccessorTest {
+ /** Random. */
+ private Random rnd;
+
+ /**
+ *
+ */
+ @BeforeEach
+ public void initRandom() {
+ long seed = System.currentTimeMillis();
+
+ System.out.println("Using seed: " + seed + "L;");
+
+ rnd = new Random(seed);
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testFieldAccessor() throws Exception {
+ Column[] cols = new Column[] {
+ new Column("pByteCol", BYTE, false),
+ new Column("pShortCol", SHORT, false),
+ new Column("pIntCol", INTEGER, false),
+ new Column("pLongCol", LONG, false),
+ new Column("pFloatCol", FLOAT, false),
+ new Column("pDoubleCol", DOUBLE, false),
+
+ new Column("byteCol", BYTE, false),
+ new Column("shortCol", SHORT, false),
+ new Column("intCol", INTEGER, false),
+ new Column("longCol", LONG, false),
+ new Column("floatCol", FLOAT, false),
+ new Column("doubleCol", DOUBLE, false),
+
+ new Column("uuidCol", UUID, false),
+ new Column("bitmaskCol", Bitmask.of(9), false),
+ new Column("stringCol", STRING, false),
+ new Column("bytesCol", BYTES, false),
+ };
+
+ final Pair<TupleAssembler, Tuple> mocks = createMocks();
+
+ final TupleAssembler tupleAssembler = mocks.getFirst();
+ final Tuple tuple = mocks.getSecond();
+
+ final TestObject obj = TestObject.randomObject(rnd);
+
+ for (int i = 0; i < cols.length; i++) {
+ UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestObject.class, cols[i], i);
+
+ accessor.write(obj, tupleAssembler);
+ }
+
+ final TestObject restoredObj = new TestObject();
+
+ for (int i = 0; i < cols.length; i++) {
+ UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestObject.class, cols[i], i);
+
+ accessor.read(restoredObj, tuple);
+ }
+
+ assertEquals(obj.pByteCol, restoredObj.pByteCol);
+ assertEquals(obj.pShortCol, restoredObj.pShortCol);
+ assertEquals(obj.pIntCol, restoredObj.pIntCol);
+ assertEquals(obj.pLongCol, restoredObj.pLongCol);
+ assertEquals(obj.pFloatCol, restoredObj.pFloatCol);
+ assertEquals(obj.pDoubleCol, restoredObj.pDoubleCol);
+
+ assertEquals(obj.byteCol, restoredObj.byteCol);
+ assertEquals(obj.shortCol, restoredObj.shortCol);
+ assertEquals(obj.intCol, restoredObj.intCol);
+ assertEquals(obj.longCol, restoredObj.longCol);
+ assertEquals(obj.floatCol, restoredObj.floatCol);
+ assertEquals(obj.doubleCol, restoredObj.doubleCol);
+
+ assertEquals(obj.uuidCol, restoredObj.uuidCol);
+ assertEquals(obj.bitmaskCol, restoredObj.bitmaskCol);
+ assertEquals(obj.stringCol, restoredObj.stringCol);
+ assertArrayEquals(obj.bytesCol, restoredObj.bytesCol);
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testNullableFieldsAccessor() throws Exception {
+ Column[] cols = new Column[] {
+ new Column("intCol", INTEGER, true),
+ new Column("longCol", LONG, true),
+
+ new Column("stringCol", STRING, true),
+ new Column("bytesCol", BYTES, true),
+ };
+
+ final Pair<TupleAssembler, Tuple> mocks = createMocks();
+
+ final TupleAssembler tupleAssembler = mocks.getFirst();
+ final Tuple tuple = mocks.getSecond();
+
+ final TestSimpleObject obj = new TestSimpleObject();
+ obj.longCol = rnd.nextLong();
+ obj.stringCol = TestUtils.randomString(rnd, 255);
+
+ for (int i = 0; i < cols.length; i++) {
+ UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestSimpleObject.class, cols[i], i);
+
+ accessor.write(obj, tupleAssembler);
+ }
+
+ final TestSimpleObject restoredObj = new TestSimpleObject();
+
+ for (int i = 0; i < cols.length; i++) {
+ UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestSimpleObject.class, cols[i], i);
+
+ accessor.read(restoredObj, tuple);
+ }
+
+ assertEquals(obj.intCol, restoredObj.intCol);
+ assertEquals(obj.longCol, restoredObj.longCol);
+
+ assertEquals(obj.stringCol, restoredObj.stringCol);
+ assertArrayEquals(obj.bytesCol, restoredObj.bytesCol);
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testIdentityAccessor() throws Exception {
+ final UnsafeFieldAccessor accessor = UnsafeFieldAccessor.createIdentityAccessor(
+ new Column("col0", STRING, true),
+ 0,
+ BinaryMode.STRING);
+
+ assertEquals("Some string", accessor.value("Some string"));
+
+ final Pair<TupleAssembler, Tuple> mocks = createMocks();
+
+ accessor.write("Other string", mocks.getFirst());
+ assertEquals("Other string", accessor.read(mocks.getSecond()));
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testWrongIdentityAccessor() throws Exception {
+ final UnsafeFieldAccessor accessor = UnsafeFieldAccessor.createIdentityAccessor(
+ new Column("col0", STRING, true),
+ 42,
+ BinaryMode.UUID);
+
+ assertEquals("Some string", accessor.value("Some string"));
+
+ final Pair<TupleAssembler, Tuple> mocks = createMocks();
+
+ assertThrows(
+ SerializationException.class,
+ () -> accessor.write("Other string", mocks.getFirst()),
+ "Failed to write field [id=42]"
+ );
+ }
+
+ /**
+ * Creates mock pair for {@link Tuple} and {@link TupleAssembler).
+ *
+ * @return Pair of mocks.
+ */
+ private Pair<TupleAssembler, Tuple> createMocks() {
+ final ArrayList<Object> vals = new ArrayList<>();
+
+ final TupleAssembler mockedAsm = Mockito.mock(TupleAssembler.class);
+ final Tuple mockedTuple = Mockito.mock(Tuple.class);
+
+ final Answer<Void> asmAnswer = new Answer<Void>() {
+ @Override public Void answer(InvocationOnMock invocation) {
+ if ("appendNull".equals(invocation.getMethod().getName()))
+ vals.add(null);
+ else
+ vals.add(invocation.getArguments()[0]);
+
+ return null;
+ }
+ };
+
+ final Answer<Object> tupleAnswer = new Answer<Object>() {
+ @Override public Object answer(InvocationOnMock invocation) {
+ final int idx = invocation.getArgumentAt(0, Integer.class);
+
+ return vals.get(idx);
+ }
+ };
+
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendNull();
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendByte(Mockito.anyByte());
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendShort(Mockito.anyShort());
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendInt(Mockito.anyInt());
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendLong(Mockito.anyLong());
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendFloat(Mockito.anyFloat());
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendDouble(Mockito.anyDouble());
+
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendUuid(Mockito.any(java.util.UUID.class));
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendBitmask(Mockito.any(BitSet.class));
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendString(Mockito.anyString());
+ Mockito.doAnswer(asmAnswer).when(mockedAsm).appendBytes(Mockito.any(byte[].class));
+
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).byteValue(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).byteValueBoxed(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).shortValue(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).shortValueBoxed(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).intValue(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).intValueBoxed(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).longValue(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).longValueBoxed(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).floatValue(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).floatValueBoxed(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).doubleValue(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).doubleValueBoxed(Mockito.anyInt());
+
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).uuidValue(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).bitmaskValue(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).stringValue(Mockito.anyInt());
+ Mockito.doAnswer(tupleAnswer).when(mockedTuple).bytesValue(Mockito.anyInt());
+
+ return new Pair<>(mockedAsm, mockedTuple);
+ }
+
+ /**
+ * Test object.
+ */
+ private static class TestObject {
+ /**
+ * @return Random TestObject.
+ */
+ public static TestObject randomObject(Random rnd) {
+ final TestObject obj = new TestObject();
+
+ obj.pByteCol = (byte)rnd.nextInt(255);
+ obj.pShortCol = (short)rnd.nextInt(65535);
+ obj.pIntCol = rnd.nextInt();
+ obj.pLongCol = rnd.nextLong();
+ obj.pFloatCol = rnd.nextFloat();
+ obj.pDoubleCol = rnd.nextDouble();
+
+ obj.byteCol = (byte)rnd.nextInt(255);
+ obj.shortCol = (short)rnd.nextInt(65535);
+ obj.intCol = rnd.nextInt();
+ obj.longCol = rnd.nextLong();
+ obj.floatCol = rnd.nextFloat();
+ obj.doubleCol = rnd.nextDouble();
+
+ obj.uuidCol = new UUID(rnd.nextLong(), rnd.nextLong());
+ obj.bitmaskCol = TestUtils.randomBitSet(rnd, rnd.nextInt(42));
+ obj.stringCol = TestUtils.randomString(rnd, rnd.nextInt(255));
+ obj.bytesCol = TestUtils.randomBytes(rnd, rnd.nextInt(255));
+
+ return obj;
+ }
+
+ // Primitive typed
+ private byte pByteCol;
+ private short pShortCol;
+ private int pIntCol;
+ private long pLongCol;
+ private float pFloatCol;
+ private double pDoubleCol;
+
+ // Reference typed
+ private Byte byteCol;
+ private Short shortCol;
+ private Integer intCol;
+ private Long longCol;
+ private Float floatCol;
+ private Double doubleCol;
+
+ private UUID uuidCol;
+ private BitSet bitmaskCol;
+ private String stringCol;
+ private byte[] bytesCol;
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ TestObject object = (TestObject)o;
+ return pByteCol == object.pByteCol &&
+ pShortCol == object.pShortCol &&
+ pIntCol == object.pIntCol &&
+ pLongCol == object.pLongCol &&
+ Float.compare(object.pFloatCol, pFloatCol) == 0 &&
+ Double.compare(object.pDoubleCol, pDoubleCol) == 0 &&
+ Objects.equals(byteCol, object.byteCol) &&
+ Objects.equals(shortCol, object.shortCol) &&
+ Objects.equals(intCol, object.intCol) &&
+ Objects.equals(longCol, object.longCol) &&
+ Objects.equals(floatCol, object.floatCol) &&
+ Objects.equals(doubleCol, object.doubleCol) &&
+ Objects.equals(uuidCol, object.uuidCol) &&
+ Objects.equals(bitmaskCol, object.bitmaskCol) &&
+ Objects.equals(stringCol, object.stringCol) &&
+ Arrays.equals(bytesCol, object.bytesCol);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return 73;
+ }
+ }
+
+ /**
+ * Test object.
+ */
+ private static class TestSimpleObject {
+ Long longCol;
+ Integer intCol;
+ byte[] bytesCol;
+ String stringCol;
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ TestSimpleObject object = (TestSimpleObject)o;
+ return Objects.equals(longCol, object.longCol) &&
+ Objects.equals(intCol, object.intCol) &&
+ Arrays.equals(bytesCol, object.bytesCol) &&
+ Objects.equals(stringCol, object.stringCol);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return 42;
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index d825ae1..2d460d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,12 +48,17 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <!-- Dependencies versions. -->
+ <janino.version>3.0.11</janino.version>
+ <javax.annotation.api.version>1.3.2</javax.annotation.api.version>
<jetbrains.annotations.version>20.1.0</jetbrains.annotations.version>
+ <jmh.framework.verion>1.9.3</jmh.framework.verion>
<junit.jupiter.version>5.7.0</junit.jupiter.version>
+ <mockito.version>1.10.19</mockito.version>
+ <!-- Maven plugins -->
<maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
<maven.surefire.plugin.version>3.0.0-M4</maven.surefire.plugin.version>
-
<apache.rat.plugin.version>0.13</apache.rat.plugin.version>
</properties>