You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2015/12/11 11:00:50 UTC
[45/50] [abbrv] ignite git commit: ignite-2065: rename "portable"
packages to "binary"
http://git-wip-us.apache.org/repos/asf/ignite/blob/1dbf20e0/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableClassDescriptor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableClassDescriptor.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableClassDescriptor.java
new file mode 100644
index 0000000..9b4d444
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableClassDescriptor.java
@@ -0,0 +1,813 @@
+/*
+ * 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.binary;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.UUID;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.binary.BinaryIdMapper;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinarySerializer;
+import org.apache.ignite.binary.Binarylizable;
+import org.apache.ignite.internal.processors.cache.CacheObjectImpl;
+import org.apache.ignite.internal.util.GridUnsafe;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.marshaller.MarshallerExclusions;
+import org.apache.ignite.marshaller.optimized.OptimizedMarshaller;
+import org.jetbrains.annotations.Nullable;
+import sun.misc.Unsafe;
+
+import static java.lang.reflect.Modifier.isStatic;
+import static java.lang.reflect.Modifier.isTransient;
+
+/**
+ * Portable class descriptor.
+ */
+public class PortableClassDescriptor {
+ /** */
+ public static final Unsafe UNSAFE = GridUnsafe.unsafe();
+
+ /** */
+ private final PortableContext ctx;
+
+ /** */
+ private final Class<?> cls;
+
+ /** */
+ private final BinarySerializer serializer;
+
+ /** ID mapper. */
+ private final BinaryIdMapper idMapper;
+
+ /** */
+ private final BinaryWriteMode mode;
+
+ /** */
+ private final boolean userType;
+
+ /** */
+ private final int typeId;
+
+ /** */
+ private final String typeName;
+
+ /** Affinity key field name. */
+ private final String affKeyFieldName;
+
+ /** */
+ private final Constructor<?> ctor;
+
+ /** */
+ private final BinaryFieldAccessor[] fields;
+
+ /** */
+ private final Method writeReplaceMtd;
+
+ /** */
+ private final Method readResolveMtd;
+
+ /** */
+ private final Map<String, Integer> stableFieldsMeta;
+
+ /** Object schemas. Initialized only for serializable classes and contains only 1 entry. */
+ private final PortableSchema stableSchema;
+
+ /** Schema registry. */
+ private final PortableSchemaRegistry schemaReg;
+
+ /** */
+ private final boolean registered;
+
+ /** */
+ private final boolean useOptMarshaller;
+
+ /** */
+ private final boolean excluded;
+
+ /**
+ * @param ctx Context.
+ * @param cls Class.
+ * @param userType User type flag.
+ * @param typeId Type ID.
+ * @param typeName Type name.
+ * @param affKeyFieldName Affinity key field name.
+ * @param idMapper ID mapper.
+ * @param serializer Serializer.
+ * @param metaDataEnabled Metadata enabled flag.
+ * @param registered Whether typeId has been successfully registered by MarshallerContext or not.
+ * @param predefined Whether the class is predefined or not.
+ * @throws BinaryObjectException In case of error.
+ */
+ PortableClassDescriptor(
+ PortableContext ctx,
+ Class<?> cls,
+ boolean userType,
+ int typeId,
+ String typeName,
+ @Nullable String affKeyFieldName,
+ @Nullable BinaryIdMapper idMapper,
+ @Nullable BinarySerializer serializer,
+ boolean metaDataEnabled,
+ boolean registered,
+ boolean predefined
+ ) throws BinaryObjectException {
+ assert ctx != null;
+ assert cls != null;
+ assert idMapper != null;
+
+ this.ctx = ctx;
+ this.cls = cls;
+ this.typeId = typeId;
+ this.userType = userType;
+ this.typeName = typeName;
+ this.affKeyFieldName = affKeyFieldName;
+ this.serializer = serializer;
+ this.idMapper = idMapper;
+ this.registered = registered;
+
+ schemaReg = ctx.schemaRegistry(typeId);
+
+ excluded = MarshallerExclusions.isExcluded(cls);
+
+ useOptMarshaller = !predefined && initUseOptimizedMarshallerFlag();
+
+ if (excluded)
+ mode = BinaryWriteMode.EXCLUSION;
+ else {
+ if (cls == BinaryEnumObjectImpl.class)
+ mode = BinaryWriteMode.PORTABLE_ENUM;
+ else
+ mode = serializer != null ? BinaryWriteMode.PORTABLE : PortableUtils.mode(cls);
+ }
+
+ switch (mode) {
+ case P_BYTE:
+ case P_BOOLEAN:
+ case P_SHORT:
+ case P_CHAR:
+ case P_INT:
+ case P_LONG:
+ case P_FLOAT:
+ case P_DOUBLE:
+ case BYTE:
+ case SHORT:
+ case INT:
+ case LONG:
+ case FLOAT:
+ case DOUBLE:
+ case CHAR:
+ case BOOLEAN:
+ case DECIMAL:
+ case STRING:
+ case UUID:
+ case DATE:
+ case TIMESTAMP:
+ case BYTE_ARR:
+ case SHORT_ARR:
+ case INT_ARR:
+ case LONG_ARR:
+ case FLOAT_ARR:
+ case DOUBLE_ARR:
+ case CHAR_ARR:
+ case BOOLEAN_ARR:
+ case DECIMAL_ARR:
+ case STRING_ARR:
+ case UUID_ARR:
+ case DATE_ARR:
+ case TIMESTAMP_ARR:
+ case OBJECT_ARR:
+ case COL:
+ case MAP:
+ case PORTABLE_OBJ:
+ case ENUM:
+ case PORTABLE_ENUM:
+ case ENUM_ARR:
+ case CLASS:
+ case EXCLUSION:
+ ctor = null;
+ fields = null;
+ stableFieldsMeta = null;
+ stableSchema = null;
+
+ break;
+
+ case PORTABLE:
+ case EXTERNALIZABLE:
+ ctor = constructor(cls);
+ fields = null;
+ stableFieldsMeta = null;
+ stableSchema = null;
+
+ break;
+
+ case OBJECT:
+ // Must not use constructor to honor transient fields semantics.
+ ctor = null;
+ ArrayList<BinaryFieldAccessor> fields0 = new ArrayList<>();
+ stableFieldsMeta = metaDataEnabled ? new HashMap<String, Integer>() : null;
+
+ PortableSchema.Builder schemaBuilder = PortableSchema.Builder.newBuilder();
+
+ Collection<String> names = new HashSet<>();
+ Collection<Integer> ids = new HashSet<>();
+
+ for (Class<?> c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) {
+ for (Field f : c.getDeclaredFields()) {
+ int mod = f.getModifiers();
+
+ if (!isStatic(mod) && !isTransient(mod)) {
+ f.setAccessible(true);
+
+ String name = f.getName();
+
+ if (!names.add(name))
+ throw new BinaryObjectException("Duplicate field name [fieldName=" + name +
+ ", cls=" + cls.getName() + ']');
+
+ int fieldId = idMapper.fieldId(typeId, name);
+
+ if (!ids.add(fieldId))
+ throw new BinaryObjectException("Duplicate field ID: " + name);
+
+ BinaryFieldAccessor fieldInfo = BinaryFieldAccessor.create(f, fieldId);
+
+ fields0.add(fieldInfo);
+
+ schemaBuilder.addField(fieldId);
+
+ if (metaDataEnabled)
+ stableFieldsMeta.put(name, fieldInfo.mode().typeId());
+ }
+ }
+ }
+
+ fields = fields0.toArray(new BinaryFieldAccessor[fields0.size()]);
+
+ stableSchema = schemaBuilder.build();
+
+ break;
+
+ default:
+ // Should never happen.
+ throw new BinaryObjectException("Invalid mode: " + mode);
+ }
+
+ if (mode == BinaryWriteMode.PORTABLE || mode == BinaryWriteMode.EXTERNALIZABLE ||
+ mode == BinaryWriteMode.OBJECT) {
+ readResolveMtd = U.findNonPublicMethod(cls, "readResolve");
+ writeReplaceMtd = U.findNonPublicMethod(cls, "writeReplace");
+ }
+ else {
+ readResolveMtd = null;
+ writeReplaceMtd = null;
+ }
+ }
+
+ /**
+ * @return {@code True} if enum.
+ */
+ boolean isEnum() {
+ return mode == BinaryWriteMode.ENUM;
+ }
+
+ /**
+ * @return Described class.
+ */
+ Class<?> describedClass() {
+ return cls;
+ }
+
+ /**
+ * @return Type ID.
+ */
+ public int typeId() {
+ return typeId;
+ }
+
+ /**
+ * @return User type flag.
+ */
+ public boolean userType() {
+ return userType;
+ }
+
+ /**
+ * @return Fields meta data.
+ */
+ Map<String, Integer> fieldsMeta() {
+ return stableFieldsMeta;
+ }
+
+ /**
+ * @return Schema.
+ */
+ PortableSchema schema() {
+ return stableSchema;
+ }
+
+ /**
+ * @return Whether typeId has been successfully registered by MarshallerContext or not.
+ */
+ public boolean registered() {
+ return registered;
+ }
+
+ /**
+ * @return {@code true} if {@link OptimizedMarshaller} must be used instead of {@link BinaryMarshaller}
+ * for object serialization and deserialization.
+ */
+ public boolean useOptimizedMarshaller() {
+ return useOptMarshaller;
+ }
+
+ /**
+ * Checks whether the class values are explicitly excluded from marshalling.
+ *
+ * @return {@code true} if excluded, {@code false} otherwise.
+ */
+ public boolean excluded() {
+ return excluded;
+ }
+
+ /**
+ * @return portableWriteReplace() method
+ */
+ @Nullable Method getWriteReplaceMethod() {
+ return writeReplaceMtd;
+ }
+
+ /**
+ * @return portableReadResolve() method
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ @Nullable Method getReadResolveMethod() {
+ return readResolveMtd;
+ }
+
+ /**
+ * @param obj Object.
+ * @param writer Writer.
+ * @throws BinaryObjectException In case of error.
+ */
+ void write(Object obj, BinaryWriterExImpl writer) throws BinaryObjectException {
+ assert obj != null;
+ assert writer != null;
+
+ writer.typeId(typeId);
+
+ switch (mode) {
+ case P_BYTE:
+ case BYTE:
+ writer.writeByteFieldPrimitive((byte) obj);
+
+ break;
+
+ case P_SHORT:
+ case SHORT:
+ writer.writeShortFieldPrimitive((short)obj);
+
+ break;
+
+ case P_INT:
+ case INT:
+ writer.writeIntFieldPrimitive((int) obj);
+
+ break;
+
+ case P_LONG:
+ case LONG:
+ writer.writeLongFieldPrimitive((long) obj);
+
+ break;
+
+ case P_FLOAT:
+ case FLOAT:
+ writer.writeFloatFieldPrimitive((float) obj);
+
+ break;
+
+ case P_DOUBLE:
+ case DOUBLE:
+ writer.writeDoubleFieldPrimitive((double) obj);
+
+ break;
+
+ case P_CHAR:
+ case CHAR:
+ writer.writeCharFieldPrimitive((char) obj);
+
+ break;
+
+ case P_BOOLEAN:
+ case BOOLEAN:
+ writer.writeBooleanFieldPrimitive((boolean) obj);
+
+ break;
+
+ case DECIMAL:
+ writer.doWriteDecimal((BigDecimal)obj);
+
+ break;
+
+ case STRING:
+ writer.doWriteString((String)obj);
+
+ break;
+
+ case UUID:
+ writer.doWriteUuid((UUID)obj);
+
+ break;
+
+ case DATE:
+ writer.doWriteDate((Date)obj);
+
+ break;
+
+ case TIMESTAMP:
+ writer.doWriteTimestamp((Timestamp)obj);
+
+ break;
+
+ case BYTE_ARR:
+ writer.doWriteByteArray((byte[])obj);
+
+ break;
+
+ case SHORT_ARR:
+ writer.doWriteShortArray((short[]) obj);
+
+ break;
+
+ case INT_ARR:
+ writer.doWriteIntArray((int[]) obj);
+
+ break;
+
+ case LONG_ARR:
+ writer.doWriteLongArray((long[]) obj);
+
+ break;
+
+ case FLOAT_ARR:
+ writer.doWriteFloatArray((float[]) obj);
+
+ break;
+
+ case DOUBLE_ARR:
+ writer.doWriteDoubleArray((double[]) obj);
+
+ break;
+
+ case CHAR_ARR:
+ writer.doWriteCharArray((char[]) obj);
+
+ break;
+
+ case BOOLEAN_ARR:
+ writer.doWriteBooleanArray((boolean[]) obj);
+
+ break;
+
+ case DECIMAL_ARR:
+ writer.doWriteDecimalArray((BigDecimal[]) obj);
+
+ break;
+
+ case STRING_ARR:
+ writer.doWriteStringArray((String[]) obj);
+
+ break;
+
+ case UUID_ARR:
+ writer.doWriteUuidArray((UUID[]) obj);
+
+ break;
+
+ case DATE_ARR:
+ writer.doWriteDateArray((Date[]) obj);
+
+ break;
+
+ case TIMESTAMP_ARR:
+ writer.doWriteTimestampArray((Timestamp[]) obj);
+
+ break;
+
+ case OBJECT_ARR:
+ writer.doWriteObjectArray((Object[])obj);
+
+ break;
+
+ case COL:
+ writer.doWriteCollection((Collection<?>)obj);
+
+ break;
+
+ case MAP:
+ writer.doWriteMap((Map<?, ?>)obj);
+
+ break;
+
+ case ENUM:
+ writer.doWriteEnum((Enum<?>)obj);
+
+ break;
+
+ case PORTABLE_ENUM:
+ writer.doWritePortableEnum((BinaryEnumObjectImpl)obj);
+
+ break;
+
+ case ENUM_ARR:
+ writer.doWriteEnumArray((Object[])obj);
+
+ break;
+
+ case CLASS:
+ writer.doWriteClass((Class)obj);
+
+ break;
+
+ case PORTABLE_OBJ:
+ writer.doWritePortableObject((BinaryObjectImpl)obj);
+
+ break;
+
+ case PORTABLE:
+ if (preWrite(writer, obj)) {
+ try {
+ if (serializer != null)
+ serializer.writeBinary(obj, writer);
+ else
+ ((Binarylizable)obj).writeBinary(writer);
+
+ postWrite(writer, obj);
+
+ // Check whether we need to update metadata.
+ if (obj.getClass() != BinaryMetadata.class) {
+ int schemaId = writer.schemaId();
+
+ if (schemaReg.schema(schemaId) == null) {
+ // This is new schema, let's update metadata.
+ BinaryMetadataCollector collector =
+ new BinaryMetadataCollector(typeId, typeName, idMapper);
+
+ if (serializer != null)
+ serializer.writeBinary(obj, collector);
+ else
+ ((Binarylizable)obj).writeBinary(collector);
+
+ PortableSchema newSchema = collector.schema();
+
+ BinaryMetadata meta = new BinaryMetadata(typeId, typeName, collector.meta(),
+ affKeyFieldName, Collections.singleton(newSchema), false);
+
+ ctx.updateMetadata(typeId, meta);
+
+ schemaReg.addSchema(newSchema.schemaId(), newSchema);
+ }
+ }
+ }
+ finally {
+ writer.popSchema();
+ }
+ }
+
+ break;
+
+ case EXTERNALIZABLE:
+ if (preWrite(writer, obj)) {
+ writer.rawWriter();
+
+ try {
+ ((Externalizable)obj).writeExternal(writer);
+
+ postWrite(writer, obj);
+ }
+ catch (IOException e) {
+ throw new BinaryObjectException("Failed to write Externalizable object: " + obj, e);
+ }
+ finally {
+ writer.popSchema();
+ }
+ }
+
+ break;
+
+ case OBJECT:
+ if (preWrite(writer, obj)) {
+ try {
+ for (BinaryFieldAccessor info : fields)
+ info.write(obj, writer);
+
+ writer.schemaId(stableSchema.schemaId());
+
+ postWrite(writer, obj);
+ }
+ finally {
+ writer.popSchema();
+ }
+ }
+
+ break;
+
+ default:
+ assert false : "Invalid mode: " + mode;
+ }
+ }
+
+ /**
+ * @param reader Reader.
+ * @return Object.
+ * @throws BinaryObjectException If failed.
+ */
+ Object read(BinaryReaderExImpl reader) throws BinaryObjectException {
+ assert reader != null;
+
+ Object res;
+
+ switch (mode) {
+ case PORTABLE:
+ res = newInstance();
+
+ reader.setHandle(res);
+
+ if (serializer != null)
+ serializer.readBinary(res, reader);
+ else
+ ((Binarylizable)res).readBinary(reader);
+
+ break;
+
+ case EXTERNALIZABLE:
+ res = newInstance();
+
+ reader.setHandle(res);
+
+ try {
+ ((Externalizable)res).readExternal(reader);
+ }
+ catch (IOException | ClassNotFoundException e) {
+ throw new BinaryObjectException("Failed to read Externalizable object: " +
+ res.getClass().getName(), e);
+ }
+
+ break;
+
+ case OBJECT:
+ res = newInstance();
+
+ reader.setHandle(res);
+
+ for (BinaryFieldAccessor info : fields)
+ info.read(res, reader);
+
+ break;
+
+ default:
+ assert false : "Invalid mode: " + mode;
+
+ return null;
+ }
+
+ if (readResolveMtd != null) {
+ try {
+ res = readResolveMtd.invoke(res);
+
+ reader.setHandle(res);
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ catch (InvocationTargetException e) {
+ if (e.getTargetException() instanceof BinaryObjectException)
+ throw (BinaryObjectException)e.getTargetException();
+
+ throw new BinaryObjectException("Failed to execute readResolve() method on " + res, e);
+ }
+ }
+
+ return res;
+ }
+
+ /**
+ * Pre-write phase.
+ *
+ * @param writer Writer.
+ * @param obj Object.
+ * @return Whether further write is needed.
+ */
+ private boolean preWrite(BinaryWriterExImpl writer, Object obj) {
+ if (writer.tryWriteAsHandle(obj))
+ return false;
+
+ writer.preWrite(registered ? null : cls.getName());
+
+ return true;
+ }
+
+ /**
+ * Post-write phase.
+ *
+ * @param writer Writer.
+ * @param obj Object.
+ */
+ private void postWrite(BinaryWriterExImpl writer, Object obj) {
+ writer.postWrite(userType, registered, obj instanceof CacheObjectImpl ? 0 : obj.hashCode());
+ }
+
+ /**
+ * @return Instance.
+ * @throws BinaryObjectException In case of error.
+ */
+ private Object newInstance() throws BinaryObjectException {
+ try {
+ return ctor != null ? ctor.newInstance() : UNSAFE.allocateInstance(cls);
+ }
+ catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
+ throw new BinaryObjectException("Failed to instantiate instance: " + cls, e);
+ }
+ }
+
+ /**
+ * @param cls Class.
+ * @return Constructor.
+ * @throws BinaryObjectException If constructor doesn't exist.
+ */
+ @SuppressWarnings("ConstantConditions")
+ @Nullable private static Constructor<?> constructor(Class<?> cls) throws BinaryObjectException {
+ assert cls != null;
+
+ try {
+ Constructor<?> ctor = U.forceEmptyConstructor(cls);
+
+ if (ctor == null)
+ throw new BinaryObjectException("Failed to find empty constructor for class: " + cls.getName());
+
+ ctor.setAccessible(true);
+
+ return ctor;
+ }
+ catch (IgniteCheckedException e) {
+ throw new BinaryObjectException("Failed to get constructor for class: " + cls.getName(), e);
+ }
+ }
+
+ /**
+ * Determines whether to use {@link OptimizedMarshaller} for serialization or
+ * not.
+ *
+ * @return {@code true} if to use, {@code false} otherwise.
+ */
+ @SuppressWarnings("unchecked")
+ private boolean initUseOptimizedMarshallerFlag() {
+ for (Class c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) {
+ try {
+ Method writeObj = c.getDeclaredMethod("writeObject", ObjectOutputStream.class);
+ Method readObj = c.getDeclaredMethod("readObject", ObjectInputStream.class);
+
+ if (!Modifier.isStatic(writeObj.getModifiers()) && !Modifier.isStatic(readObj.getModifiers()) &&
+ writeObj.getReturnType() == void.class && readObj.getReturnType() == void.class)
+ return true;
+ }
+ catch (NoSuchMethodException ignored) {
+ // No-op.
+ }
+ }
+
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1dbf20e0/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableContext.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableContext.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableContext.java
new file mode 100644
index 0000000..f7375a4
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableContext.java
@@ -0,0 +1,1102 @@
+/*
+ * 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.binary;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.binary.BinaryIdMapper;
+import org.apache.ignite.binary.BinaryInvalidTypeException;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinarySerializer;
+import org.apache.ignite.binary.BinaryType;
+import org.apache.ignite.binary.BinaryTypeConfiguration;
+import org.apache.ignite.cache.CacheKeyConfiguration;
+import org.apache.ignite.cache.affinity.AffinityKeyMapped;
+import org.apache.ignite.configuration.BinaryConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteKernal;
+import org.apache.ignite.internal.IgnitionEx;
+import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
+import org.apache.ignite.internal.processors.datastructures.CollocatedQueueItemKey;
+import org.apache.ignite.internal.processors.datastructures.CollocatedSetItemKey;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.lang.GridMapEntry;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.T2;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.marshaller.MarshallerContext;
+import org.apache.ignite.marshaller.optimized.OptimizedMarshaller;
+import org.jetbrains.annotations.Nullable;
+import org.jsr166.ConcurrentHashMap8;
+
+import java.io.Externalizable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.ObjectStreamException;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentMap;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Portable context.
+ */
+public class PortableContext implements Externalizable {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** */
+ private static final ClassLoader dfltLdr = U.gridClassLoader();
+
+ /** */
+ private final ConcurrentMap<Class<?>, PortableClassDescriptor> descByCls = new ConcurrentHashMap8<>();
+
+ /** Holds classes loaded by default class loader only. */
+ private final ConcurrentMap<Integer, PortableClassDescriptor> userTypes = new ConcurrentHashMap8<>();
+
+ /** */
+ private final Map<Integer, PortableClassDescriptor> predefinedTypes = new HashMap<>();
+
+ /** */
+ private final Map<String, Integer> predefinedTypeNames = new HashMap<>();
+
+ /** */
+ private final Map<Class<? extends Collection>, Byte> colTypes = new HashMap<>();
+
+ /** */
+ private final Map<Class<? extends Map>, Byte> mapTypes = new HashMap<>();
+
+ /** */
+ private final ConcurrentMap<Integer, BinaryIdMapper> mappers = new ConcurrentHashMap8<>(0);
+
+ /** Affinity key field names. */
+ private final ConcurrentMap<Integer, String> affKeyFieldNames = new ConcurrentHashMap8<>(0);
+
+ /** */
+ private final Map<String, BinaryIdMapper> typeMappers = new ConcurrentHashMap8<>(0);
+
+ /** */
+ private BinaryMetadataHandler metaHnd;
+
+ /** Actual marshaller. */
+ private BinaryMarshaller marsh;
+
+ /** */
+ private MarshallerContext marshCtx;
+
+ /** */
+ private String gridName;
+
+ /** */
+ private IgniteConfiguration igniteCfg;
+
+ /** */
+ private final OptimizedMarshaller optmMarsh = new OptimizedMarshaller();
+
+ /** Compact footer flag. */
+ private boolean compactFooter;
+
+ /** Object schemas. */
+ private volatile Map<Integer, PortableSchemaRegistry> schemas;
+
+ /**
+ * For {@link Externalizable}.
+ */
+ public PortableContext() {
+ // No-op.
+ }
+
+ /**
+ * @param metaHnd Meta data handler.
+ * @param igniteCfg Ignite configuration.
+ */
+ public PortableContext(BinaryMetadataHandler metaHnd, IgniteConfiguration igniteCfg) {
+ assert metaHnd != null;
+ assert igniteCfg != null;
+
+ this.metaHnd = metaHnd;
+ this.igniteCfg = igniteCfg;
+
+ gridName = igniteCfg.getGridName();
+
+ colTypes.put(ArrayList.class, GridPortableMarshaller.ARR_LIST);
+ colTypes.put(LinkedList.class, GridPortableMarshaller.LINKED_LIST);
+ colTypes.put(HashSet.class, GridPortableMarshaller.HASH_SET);
+ colTypes.put(LinkedHashSet.class, GridPortableMarshaller.LINKED_HASH_SET);
+
+ mapTypes.put(HashMap.class, GridPortableMarshaller.HASH_MAP);
+ mapTypes.put(LinkedHashMap.class, GridPortableMarshaller.LINKED_HASH_MAP);
+
+ // IDs range from [0..200] is used by Java SDK API and GridGain legacy API
+
+ registerPredefinedType(Byte.class, GridPortableMarshaller.BYTE);
+ registerPredefinedType(Boolean.class, GridPortableMarshaller.BOOLEAN);
+ registerPredefinedType(Short.class, GridPortableMarshaller.SHORT);
+ registerPredefinedType(Character.class, GridPortableMarshaller.CHAR);
+ registerPredefinedType(Integer.class, GridPortableMarshaller.INT);
+ registerPredefinedType(Long.class, GridPortableMarshaller.LONG);
+ registerPredefinedType(Float.class, GridPortableMarshaller.FLOAT);
+ registerPredefinedType(Double.class, GridPortableMarshaller.DOUBLE);
+ registerPredefinedType(String.class, GridPortableMarshaller.STRING);
+ registerPredefinedType(BigDecimal.class, GridPortableMarshaller.DECIMAL);
+ registerPredefinedType(Date.class, GridPortableMarshaller.DATE);
+ registerPredefinedType(Timestamp.class, GridPortableMarshaller.TIMESTAMP);
+ registerPredefinedType(UUID.class, GridPortableMarshaller.UUID);
+
+ registerPredefinedType(byte[].class, GridPortableMarshaller.BYTE_ARR);
+ registerPredefinedType(short[].class, GridPortableMarshaller.SHORT_ARR);
+ registerPredefinedType(int[].class, GridPortableMarshaller.INT_ARR);
+ registerPredefinedType(long[].class, GridPortableMarshaller.LONG_ARR);
+ registerPredefinedType(float[].class, GridPortableMarshaller.FLOAT_ARR);
+ registerPredefinedType(double[].class, GridPortableMarshaller.DOUBLE_ARR);
+ registerPredefinedType(char[].class, GridPortableMarshaller.CHAR_ARR);
+ registerPredefinedType(boolean[].class, GridPortableMarshaller.BOOLEAN_ARR);
+ registerPredefinedType(BigDecimal[].class, GridPortableMarshaller.DECIMAL_ARR);
+ registerPredefinedType(String[].class, GridPortableMarshaller.STRING_ARR);
+ registerPredefinedType(UUID[].class, GridPortableMarshaller.UUID_ARR);
+ registerPredefinedType(Date[].class, GridPortableMarshaller.DATE_ARR);
+ registerPredefinedType(Timestamp[].class, GridPortableMarshaller.TIMESTAMP_ARR);
+ registerPredefinedType(Object[].class, GridPortableMarshaller.OBJ_ARR);
+
+ registerPredefinedType(ArrayList.class, 0);
+ registerPredefinedType(LinkedList.class, 0);
+ registerPredefinedType(HashSet.class, 0);
+ registerPredefinedType(LinkedHashSet.class, 0);
+
+ registerPredefinedType(HashMap.class, 0);
+ registerPredefinedType(LinkedHashMap.class, 0);
+
+ registerPredefinedType(GridMapEntry.class, 60);
+ registerPredefinedType(IgniteBiTuple.class, 61);
+ registerPredefinedType(T2.class, 62);
+
+ // IDs range [200..1000] is used by Ignite internal APIs.
+ }
+
+ /**
+ * @return Marshaller.
+ */
+ public BinaryMarshaller marshaller() {
+ return marsh;
+ }
+
+ /**
+ * @return Ignite configuration.
+ */
+ public IgniteConfiguration configuration(){
+ return igniteCfg;
+ }
+
+ /**
+ * @param marsh Portable marshaller.
+ * @param cfg Configuration.
+ * @throws BinaryObjectException In case of error.
+ */
+ public void configure(BinaryMarshaller marsh, IgniteConfiguration cfg) throws BinaryObjectException {
+ if (marsh == null)
+ return;
+
+ this.marsh = marsh;
+
+ marshCtx = marsh.getContext();
+
+ BinaryConfiguration binaryCfg = cfg.getBinaryConfiguration();
+
+ if (binaryCfg == null)
+ binaryCfg = new BinaryConfiguration();
+
+ assert marshCtx != null;
+
+ optmMarsh.setContext(marshCtx);
+
+ configure(
+ binaryCfg.getIdMapper(),
+ binaryCfg.getSerializer(),
+ binaryCfg.getTypeConfigurations()
+ );
+
+ compactFooter = binaryCfg.isCompactFooter();
+ }
+
+ /**
+ * @param globalIdMapper ID mapper.
+ * @param globalSerializer Serializer.
+ * @param typeCfgs Type configurations.
+ * @throws BinaryObjectException In case of error.
+ */
+ private void configure(
+ BinaryIdMapper globalIdMapper,
+ BinarySerializer globalSerializer,
+ Collection<BinaryTypeConfiguration> typeCfgs
+ ) throws BinaryObjectException {
+ TypeDescriptors descs = new TypeDescriptors();
+
+ Map<String, String> affFields = new HashMap<>();
+
+ if (!F.isEmpty(igniteCfg.getCacheKeyConfiguration())) {
+ for (CacheKeyConfiguration keyCfg : igniteCfg.getCacheKeyConfiguration())
+ affFields.put(keyCfg.getTypeName(), keyCfg.getAffinityKeyFieldName());
+ }
+
+ if (typeCfgs != null) {
+ for (BinaryTypeConfiguration typeCfg : typeCfgs) {
+ String clsName = typeCfg.getTypeName();
+
+ if (clsName == null)
+ throw new BinaryObjectException("Class name is required for portable type configuration.");
+
+ BinaryIdMapper idMapper = globalIdMapper;
+
+ if (typeCfg.getIdMapper() != null)
+ idMapper = typeCfg.getIdMapper();
+
+ idMapper = BinaryInternalIdMapper.create(idMapper);
+
+ BinarySerializer serializer = globalSerializer;
+
+ if (typeCfg.getSerializer() != null)
+ serializer = typeCfg.getSerializer();
+
+ if (clsName.endsWith(".*")) {
+ String pkgName = clsName.substring(0, clsName.length() - 2);
+
+ for (String clsName0 : classesInPackage(pkgName))
+ descs.add(clsName0, idMapper, serializer, affFields.get(clsName0),
+ typeCfg.isEnum(), true);
+ }
+ else
+ descs.add(clsName, idMapper, serializer, affFields.get(clsName),
+ typeCfg.isEnum(), false);
+ }
+ }
+
+ for (TypeDescriptor desc : descs.descriptors())
+ registerUserType(desc.clsName, desc.idMapper, desc.serializer, desc.affKeyFieldName, desc.isEnum);
+
+ BinaryInternalIdMapper dfltMapper = BinaryInternalIdMapper.create(globalIdMapper);
+
+ // Put affinity field names for unconfigured types.
+ for (Map.Entry<String, String> entry : affFields.entrySet()) {
+ String typeName = entry.getKey();
+
+ int typeId = dfltMapper.typeId(typeName);
+
+ affKeyFieldNames.putIfAbsent(typeId, entry.getValue());
+ }
+
+ addSystemClassAffinityKey(CollocatedSetItemKey.class);
+ addSystemClassAffinityKey(CollocatedQueueItemKey.class);
+ }
+
+ /**
+ * @param cls Class.
+ */
+ private void addSystemClassAffinityKey(Class<?> cls) {
+ String fieldName = affinityFieldName(cls);
+
+ assert fieldName != null : cls;
+
+ affKeyFieldNames.putIfAbsent(cls.getName().hashCode(), affinityFieldName(cls));
+ }
+
+ /**
+ * @param pkgName Package name.
+ * @return Class names.
+ */
+ @SuppressWarnings("ConstantConditions")
+ private static Iterable<String> classesInPackage(String pkgName) {
+ assert pkgName != null;
+
+ Collection<String> clsNames = new ArrayList<>();
+
+ ClassLoader ldr = U.gridClassLoader();
+
+ if (ldr instanceof URLClassLoader) {
+ String pkgPath = pkgName.replaceAll("\\.", "/");
+
+ URL[] urls = ((URLClassLoader)ldr).getURLs();
+
+ for (URL url : urls) {
+ String proto = url.getProtocol().toLowerCase();
+
+ if ("file".equals(proto)) {
+ try {
+ File cpElement = new File(url.toURI());
+
+ if (cpElement.isDirectory()) {
+ File pkgDir = new File(cpElement, pkgPath);
+
+ if (pkgDir.isDirectory()) {
+ for (File file : pkgDir.listFiles()) {
+ String fileName = file.getName();
+
+ if (file.isFile() && fileName.toLowerCase().endsWith(".class"))
+ clsNames.add(pkgName + '.' + fileName.substring(0, fileName.length() - 6));
+ }
+ }
+ }
+ else if (cpElement.isFile()) {
+ try {
+ JarFile jar = new JarFile(cpElement);
+
+ Enumeration<JarEntry> entries = jar.entries();
+
+ while (entries.hasMoreElements()) {
+ String entry = entries.nextElement().getName();
+
+ if (entry.startsWith(pkgPath) && entry.endsWith(".class")) {
+ String clsName = entry.substring(pkgPath.length() + 1, entry.length() - 6);
+
+ if (!clsName.contains("/") && !clsName.contains("\\"))
+ clsNames.add(pkgName + '.' + clsName);
+ }
+ }
+ }
+ catch (IOException ignored) {
+ // No-op.
+ }
+ }
+ }
+ catch (URISyntaxException ignored) {
+ // No-op.
+ }
+ }
+ }
+ }
+
+ return clsNames;
+ }
+
+ /**
+ * @param cls Class.
+ * @return Class descriptor.
+ * @throws BinaryObjectException In case of error.
+ */
+ public PortableClassDescriptor descriptorForClass(Class<?> cls, boolean deserialize)
+ throws BinaryObjectException {
+ assert cls != null;
+
+ PortableClassDescriptor desc = descByCls.get(cls);
+
+ if (desc == null || !desc.registered())
+ desc = registerClassDescriptor(cls, deserialize);
+
+ return desc;
+ }
+
+ /**
+ * @param userType User type or not.
+ * @param typeId Type ID.
+ * @param ldr Class loader.
+ * @return Class descriptor.
+ */
+ public PortableClassDescriptor descriptorForTypeId(
+ boolean userType,
+ int typeId,
+ ClassLoader ldr,
+ boolean deserialize
+ ) {
+ assert typeId != GridPortableMarshaller.UNREGISTERED_TYPE_ID;
+
+ //TODO: As a workaround for IGNITE-1358 we always check the predefined map before without checking 'userType'
+ PortableClassDescriptor desc = predefinedTypes.get(typeId);
+
+ if (desc != null)
+ return desc;
+
+ if (ldr == null)
+ ldr = dfltLdr;
+
+ // If the type hasn't been loaded by default class loader then we mustn't return the descriptor from here
+ // giving a chance to a custom class loader to reload type's class.
+ if (userType && ldr.equals(dfltLdr)) {
+ desc = userTypes.get(typeId);
+
+ if (desc != null)
+ return desc;
+ }
+
+ Class cls;
+
+ try {
+ cls = marshCtx.getClass(typeId, ldr);
+
+ desc = descByCls.get(cls);
+ }
+ catch (ClassNotFoundException e) {
+ // Class might have been loaded by default class loader.
+ if (userType && !ldr.equals(dfltLdr) && (desc = descriptorForTypeId(true, typeId, dfltLdr, deserialize)) != null)
+ return desc;
+
+ throw new BinaryInvalidTypeException(e);
+ }
+ catch (IgniteCheckedException e) {
+ // Class might have been loaded by default class loader.
+ if (userType && !ldr.equals(dfltLdr) && (desc = descriptorForTypeId(true, typeId, dfltLdr, deserialize)) != null)
+ return desc;
+
+ throw new BinaryObjectException("Failed resolve class for ID: " + typeId, e);
+ }
+
+ if (desc == null) {
+ desc = registerClassDescriptor(cls, deserialize);
+
+ assert desc.typeId() == typeId;
+ }
+
+ return desc;
+ }
+
+ /**
+ * Creates and registers {@link PortableClassDescriptor} for the given {@code class}.
+ *
+ * @param cls Class.
+ * @return Class descriptor.
+ */
+ private PortableClassDescriptor registerClassDescriptor(Class<?> cls, boolean deserialize) {
+ PortableClassDescriptor desc;
+
+ String clsName = cls.getName();
+
+ if (marshCtx.isSystemType(clsName)) {
+ desc = new PortableClassDescriptor(this,
+ cls,
+ false,
+ clsName.hashCode(),
+ clsName,
+ null,
+ BinaryInternalIdMapper.defaultInstance(),
+ null,
+ false,
+ true, /* registered */
+ false /* predefined */
+ );
+
+ PortableClassDescriptor old = descByCls.putIfAbsent(cls, desc);
+
+ if (old != null)
+ desc = old;
+ }
+ else
+ desc = registerUserClassDescriptor(cls, deserialize);
+
+ return desc;
+ }
+
+ /**
+ * Creates and registers {@link PortableClassDescriptor} for the given user {@code class}.
+ *
+ * @param cls Class.
+ * @return Class descriptor.
+ */
+ private PortableClassDescriptor registerUserClassDescriptor(Class<?> cls, boolean deserialize) {
+ boolean registered;
+
+ String typeName = typeName(cls.getName());
+
+ BinaryIdMapper idMapper = userTypeIdMapper(typeName);
+
+ int typeId = idMapper.typeId(typeName);
+
+ try {
+ registered = marshCtx.registerClass(typeId, cls);
+ }
+ catch (IgniteCheckedException e) {
+ throw new BinaryObjectException("Failed to register class.", e);
+ }
+
+ String affFieldName = affinityFieldName(cls);
+
+ PortableClassDescriptor desc = new PortableClassDescriptor(this,
+ cls,
+ true,
+ typeId,
+ typeName,
+ affFieldName,
+ idMapper,
+ null,
+ true,
+ registered,
+ false /* predefined */
+ );
+
+ if (!deserialize) {
+ Collection<PortableSchema> schemas = desc.schema() != null ? Collections.singleton(desc.schema()) : null;
+
+ metaHnd.addMeta(typeId,
+ new BinaryMetadata(typeId, typeName, desc.fieldsMeta(), affFieldName, schemas, desc.isEnum()).wrap(this));
+ }
+
+ // perform put() instead of putIfAbsent() because "registered" flag might have been changed or class loader
+ // might have reloaded described class.
+ if (IgniteUtils.detectClassLoader(cls).equals(dfltLdr))
+ userTypes.put(typeId, desc);
+
+ descByCls.put(cls, desc);
+
+ mappers.putIfAbsent(typeId, idMapper);
+
+ return desc;
+ }
+
+ /**
+ * @param cls Collection class.
+ * @return Collection type ID.
+ */
+ public byte collectionType(Class<? extends Collection> cls) {
+ assert cls != null;
+
+ Byte type = colTypes.get(cls);
+
+ if (type != null)
+ return type;
+
+ return Set.class.isAssignableFrom(cls) ? GridPortableMarshaller.USER_SET : GridPortableMarshaller.USER_COL;
+ }
+
+ /**
+ * @param cls Map class.
+ * @return Map type ID.
+ */
+ public byte mapType(Class<? extends Map> cls) {
+ assert cls != null;
+
+ Byte type = mapTypes.get(cls);
+
+ return type != null ? type : GridPortableMarshaller.USER_COL;
+ }
+
+ /**
+ * @param typeName Type name.
+ * @return Type ID.
+ */
+ public int typeId(String typeName) {
+ String typeName0 = typeName(typeName);
+
+ Integer id = predefinedTypeNames.get(typeName0);
+
+ if (id != null)
+ return id;
+
+ if (marshCtx.isSystemType(typeName))
+ return typeName.hashCode();
+
+ return userTypeIdMapper(typeName0).typeId(typeName0);
+ }
+
+ /**
+ * @param typeId Type ID.
+ * @param fieldName Field name.
+ * @return Field ID.
+ */
+ public int fieldId(int typeId, String fieldName) {
+ return userTypeIdMapper(typeId).fieldId(typeId, fieldName);
+ }
+
+ /**
+ * @param typeId Type ID.
+ * @return Instance of ID mapper.
+ */
+ public BinaryIdMapper userTypeIdMapper(int typeId) {
+ BinaryIdMapper idMapper = mappers.get(typeId);
+
+ return idMapper != null ? idMapper : BinaryInternalIdMapper.defaultInstance();
+ }
+
+ /**
+ * @param typeName Type name.
+ * @return Instance of ID mapper.
+ */
+ private BinaryIdMapper userTypeIdMapper(String typeName) {
+ BinaryIdMapper idMapper = typeMappers.get(typeName);
+
+ return idMapper != null ? idMapper : BinaryInternalIdMapper.defaultInstance();
+ }
+
+ /**
+ * @param cls Class to get affinity field for.
+ * @return Affinity field name or {@code null} if field name was not found.
+ */
+ private String affinityFieldName(Class cls) {
+ for (; cls != Object.class && cls != null; cls = cls.getSuperclass()) {
+ for (Field f : cls.getDeclaredFields()) {
+ if (f.getAnnotation(AffinityKeyMapped.class) != null)
+ return f.getName();
+ }
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ U.writeString(out, igniteCfg.getGridName());
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ gridName = U.readString(in);
+ }
+
+ /**
+ * @return Portable context.
+ * @throws ObjectStreamException In case of error.
+ */
+ protected Object readResolve() throws ObjectStreamException {
+ try {
+ IgniteKernal g = IgnitionEx.gridx(gridName);
+
+ if (g == null)
+ throw new IllegalStateException("Failed to find grid for name: " + gridName);
+
+ return ((CacheObjectBinaryProcessorImpl)g.context().cacheObjects()).portableContext();
+ }
+ catch (IllegalStateException e) {
+ throw U.withCause(new InvalidObjectException(e.getMessage()), e);
+ }
+ }
+
+ /**
+ * @param cls Class.
+ * @param id Type ID.
+ * @return GridPortableClassDescriptor.
+ */
+ public PortableClassDescriptor registerPredefinedType(Class<?> cls, int id) {
+ String typeName = typeName(cls.getName());
+
+ PortableClassDescriptor desc = new PortableClassDescriptor(
+ this,
+ cls,
+ false,
+ id,
+ typeName,
+ null,
+ BinaryInternalIdMapper.defaultInstance(),
+ null,
+ false,
+ true, /* registered */
+ true /* predefined */
+ );
+
+ predefinedTypeNames.put(typeName, id);
+ predefinedTypes.put(id, desc);
+
+ descByCls.put(cls, desc);
+
+ return desc;
+ }
+
+ /**
+ * @param clsName Class name.
+ * @param idMapper ID mapper.
+ * @param serializer Serializer.
+ * @param affKeyFieldName Affinity key field name.
+ * @param isEnum If enum.
+ * @throws BinaryObjectException In case of error.
+ */
+ @SuppressWarnings("ErrorNotRethrown")
+ public void registerUserType(String clsName,
+ BinaryIdMapper idMapper,
+ @Nullable BinarySerializer serializer,
+ @Nullable String affKeyFieldName,
+ boolean isEnum)
+ throws BinaryObjectException {
+ assert idMapper != null;
+
+ Class<?> cls = null;
+
+ try {
+ cls = Class.forName(clsName);
+ }
+ catch (ClassNotFoundException | NoClassDefFoundError ignored) {
+ // No-op.
+ }
+
+ String typeName = typeName(clsName);
+
+ int id = idMapper.typeId(typeName);
+
+ //Workaround for IGNITE-1358
+ if (predefinedTypes.get(id) != null)
+ throw new BinaryObjectException("Duplicate type ID [clsName=" + clsName + ", id=" + id + ']');
+
+ if (mappers.put(id, idMapper) != null)
+ throw new BinaryObjectException("Duplicate type ID [clsName=" + clsName + ", id=" + id + ']');
+
+ if (affKeyFieldName != null) {
+ if (affKeyFieldNames.put(id, affKeyFieldName) != null)
+ throw new BinaryObjectException("Duplicate type ID [clsName=" + clsName + ", id=" + id + ']');
+ }
+
+ typeMappers.put(typeName, idMapper);
+
+ Map<String, Integer> fieldsMeta = null;
+ Collection<PortableSchema> schemas = null;
+
+ if (cls != null) {
+ PortableClassDescriptor desc = new PortableClassDescriptor(
+ this,
+ cls,
+ true,
+ id,
+ typeName,
+ affKeyFieldName,
+ idMapper,
+ serializer,
+ true,
+ true, /* registered */
+ false /* predefined */
+ );
+
+ fieldsMeta = desc.fieldsMeta();
+ schemas = desc.schema() != null ? Collections.singleton(desc.schema()) : null;
+
+ if (IgniteUtils.detectClassLoader(cls).equals(dfltLdr))
+ userTypes.put(id, desc);
+
+ descByCls.put(cls, desc);
+ }
+
+ metaHnd.addMeta(id, new BinaryMetadata(id, typeName, fieldsMeta, affKeyFieldName, schemas, isEnum).wrap(this));
+ }
+
+ /**
+ * Create binary field.
+ *
+ * @param typeId Type ID.
+ * @param fieldName Field name.
+ * @return Binary field.
+ */
+ public BinaryFieldImpl createField(int typeId, String fieldName) {
+ PortableSchemaRegistry schemaReg = schemaRegistry(typeId);
+
+ int fieldId = userTypeIdMapper(typeId).fieldId(typeId, fieldName);
+
+ return new BinaryFieldImpl(typeId, schemaReg, fieldName, fieldId);
+ }
+
+ /**
+ * @param typeId Type ID.
+ * @return Meta data.
+ * @throws BinaryObjectException In case of error.
+ */
+ @Nullable public BinaryType metadata(int typeId) throws BinaryObjectException {
+ return metaHnd != null ? metaHnd.metadata(typeId) : null;
+ }
+
+ /**
+ * @param typeId Type ID.
+ * @return Affinity key field name.
+ */
+ public String affinityKeyFieldName(int typeId) {
+ return affKeyFieldNames.get(typeId);
+ }
+
+ /**
+ * @param typeId Type ID.
+ * @param meta Meta data.
+ * @throws BinaryObjectException In case of error.
+ */
+ public void updateMetadata(int typeId, BinaryMetadata meta) throws BinaryObjectException {
+ metaHnd.addMeta(typeId, meta.wrap(this));
+ }
+
+ /**
+ * @return Whether field IDs should be skipped in footer or not.
+ */
+ public boolean isCompactFooter() {
+ return compactFooter;
+ }
+
+ /**
+ * Get schema registry for type ID.
+ *
+ * @param typeId Type ID.
+ * @return Schema registry for type ID.
+ */
+ public PortableSchemaRegistry schemaRegistry(int typeId) {
+ Map<Integer, PortableSchemaRegistry> schemas0 = schemas;
+
+ if (schemas0 == null) {
+ synchronized (this) {
+ schemas0 = schemas;
+
+ if (schemas0 == null) {
+ schemas0 = new HashMap<>();
+
+ PortableSchemaRegistry reg = new PortableSchemaRegistry();
+
+ schemas0.put(typeId, reg);
+
+ schemas = schemas0;
+
+ return reg;
+ }
+ }
+ }
+
+ PortableSchemaRegistry reg = schemas0.get(typeId);
+
+ if (reg == null) {
+ synchronized (this) {
+ reg = schemas.get(typeId);
+
+ if (reg == null) {
+ reg = new PortableSchemaRegistry();
+
+ schemas0 = new HashMap<>(schemas);
+
+ schemas0.put(typeId, reg);
+
+ schemas = schemas0;
+ }
+ }
+ }
+
+ return reg;
+ }
+
+ /**
+ * Returns instance of {@link OptimizedMarshaller}.
+ *
+ * @return Optimized marshaller.
+ */
+ OptimizedMarshaller optimizedMarsh() {
+ return optmMarsh;
+ }
+
+ /**
+ * @param clsName Class name.
+ * @return Type name.
+ */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public static String typeName(String clsName) {
+ assert clsName != null;
+
+ int idx = clsName.lastIndexOf('$');
+
+ if (idx == clsName.length() - 1)
+ // This is a regular (not inner) class name that ends with '$'. Common use case for Scala classes.
+ idx = -1;
+ else if (idx >= 0) {
+ String typeName = clsName.substring(idx + 1);
+
+ try {
+ Integer.parseInt(typeName);
+
+ // This is an anonymous class. Don't cut off enclosing class name for it.
+ idx = -1;
+ }
+ catch (NumberFormatException ignore) {
+ // This is a lambda class.
+ if (clsName.indexOf("$$Lambda$") > 0)
+ idx = -1;
+ else
+ return typeName;
+ }
+ }
+
+ if (idx < 0)
+ idx = clsName.lastIndexOf('.');
+
+ return idx >= 0 ? clsName.substring(idx + 1) : clsName;
+ }
+
+ /**
+ * Undeployment callback invoked when class loader is being undeployed.
+ *
+ * Some marshallers may want to clean their internal state that uses the undeployed class loader somehow.
+ *
+ * @param ldr Class loader being undeployed.
+ */
+ public void onUndeploy(ClassLoader ldr) {
+ for (Class<?> cls : descByCls.keySet()) {
+ if (ldr.equals(cls.getClassLoader()))
+ descByCls.remove(cls);
+ }
+
+ U.clearClassCache(ldr);
+ }
+
+ /**
+ * Type descriptors.
+ */
+ private static class TypeDescriptors {
+ /** Descriptors map. */
+ private final Map<String, TypeDescriptor> descs = new LinkedHashMap<>();
+
+ /**
+ * Add type descriptor.
+ *
+ * @param clsName Class name.
+ * @param idMapper ID mapper.
+ * @param serializer Serializer.
+ * @param affKeyFieldName Affinity key field name.
+ * @param isEnum Enum flag.
+ * @param canOverride Whether this descriptor can be override.
+ * @throws BinaryObjectException If failed.
+ */
+ private void add(String clsName,
+ BinaryIdMapper idMapper,
+ BinarySerializer serializer,
+ String affKeyFieldName,
+ boolean isEnum,
+ boolean canOverride)
+ throws BinaryObjectException {
+ TypeDescriptor desc = new TypeDescriptor(clsName,
+ idMapper,
+ serializer,
+ affKeyFieldName,
+ isEnum,
+ canOverride);
+
+ TypeDescriptor oldDesc = descs.get(clsName);
+
+ if (oldDesc == null)
+ descs.put(clsName, desc);
+ else
+ oldDesc.override(desc);
+ }
+
+ /**
+ * Get all collected descriptors.
+ *
+ * @return Descriptors.
+ */
+ private Iterable<TypeDescriptor> descriptors() {
+ return descs.values();
+ }
+ }
+
+ /**
+ * Type descriptor.
+ */
+ private static class TypeDescriptor {
+ /** Class name. */
+ private final String clsName;
+
+ /** ID mapper. */
+ private BinaryIdMapper idMapper;
+
+ /** Serializer. */
+ private BinarySerializer serializer;
+
+ /** Affinity key field name. */
+ private String affKeyFieldName;
+
+ /** Enum flag. */
+ private boolean isEnum;
+
+ /** Whether this descriptor can be override. */
+ private boolean canOverride;
+
+ /**
+ * Constructor.
+ *
+ * @param clsName Class name.
+ * @param idMapper ID mapper.
+ * @param serializer Serializer.
+ * @param affKeyFieldName Affinity key field name.
+ * @param isEnum Enum type.
+ * @param canOverride Whether this descriptor can be override.
+ */
+ private TypeDescriptor(String clsName, BinaryIdMapper idMapper, BinarySerializer serializer,
+ String affKeyFieldName, boolean isEnum, boolean canOverride) {
+ this.clsName = clsName;
+ this.idMapper = idMapper;
+ this.serializer = serializer;
+ this.affKeyFieldName = affKeyFieldName;
+ this.isEnum = isEnum;
+ this.canOverride = canOverride;
+ }
+
+ /**
+ * Override portable class descriptor.
+ *
+ * @param other Other descriptor.
+ * @throws BinaryObjectException If failed.
+ */
+ private void override(TypeDescriptor other) throws BinaryObjectException {
+ assert clsName.equals(other.clsName);
+
+ if (canOverride) {
+ idMapper = other.idMapper;
+ serializer = other.serializer;
+ affKeyFieldName = other.affKeyFieldName;
+ canOverride = other.canOverride;
+ }
+ else if (!other.canOverride)
+ throw new BinaryObjectException("Duplicate explicit class definition in configuration: " + clsName);
+ }
+ }
+
+ /**
+ * Type id wrapper.
+ */
+ static class Type {
+ /** Type id */
+ private final int id;
+
+ /** Whether the following type is registered in a cache or not */
+ private final boolean registered;
+
+ /**
+ * @param id Id.
+ * @param registered Registered.
+ */
+ public Type(int id, boolean registered) {
+ this.id = id;
+ this.registered = registered;
+ }
+
+ /**
+ * @return Type ID.
+ */
+ public int id() {
+ return id;
+ }
+
+ /**
+ * @return Registered flag value.
+ */
+ public boolean registered() {
+ return registered;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1dbf20e0/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePositionReadable.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePositionReadable.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePositionReadable.java
new file mode 100644
index 0000000..8db6384
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePositionReadable.java
@@ -0,0 +1,47 @@
+/*
+ * 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.binary;
+
+/**
+ * Interface allowing for positioned read.
+ */
+public interface PortablePositionReadable {
+ /**
+ * Read byte at the given position.
+ *
+ * @param pos Position.
+ * @return Value.
+ */
+ public byte readBytePositioned(int pos);
+
+ /**
+ * Read short at the given position.
+ *
+ * @param pos Position.
+ * @return Value.
+ */
+ public short readShortPositioned(int pos);
+
+ /**
+ * Read integer at the given position.
+ *
+ * @param pos Position.
+ * @return Value.
+ */
+ public int readIntPositioned(int pos);
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1dbf20e0/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePrimitives.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePrimitives.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePrimitives.java
new file mode 100644
index 0000000..e5ff494
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePrimitives.java
@@ -0,0 +1,382 @@
+/*
+ * 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.binary;
+
+import org.apache.ignite.internal.util.GridUnsafe;
+import sun.misc.Unsafe;
+
+import java.nio.ByteOrder;
+
+/**
+ * Primitives writer.
+ */
+public abstract class PortablePrimitives {
+ /** */
+ private static final Unsafe UNSAFE = GridUnsafe.unsafe();
+
+ /** */
+ private static final long BYTE_ARR_OFF = UNSAFE.arrayBaseOffset(byte[].class);
+
+ /** */
+ private static final long CHAR_ARR_OFF = UNSAFE.arrayBaseOffset(char[].class);
+
+ /** Whether little endian is set. */
+ private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @param val Value.
+ */
+ public static void writeByte(byte[] arr, int off, byte val) {
+ UNSAFE.putByte(arr, BYTE_ARR_OFF + off, val);
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static byte readByte(byte[] arr, int off) {
+ return UNSAFE.getByte(arr, BYTE_ARR_OFF + off);
+ }
+
+ /**
+ * @param ptr Pointer.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static byte readByte(long ptr, int off) {
+ return UNSAFE.getByte(ptr + off);
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static byte[] readByteArray(byte[] arr, int off, int len) {
+ byte[] arr0 = new byte[len];
+
+ UNSAFE.copyMemory(arr, BYTE_ARR_OFF + off, arr0, BYTE_ARR_OFF, len);
+
+ return arr0;
+ }
+
+ /**
+ * @param ptr Pointer.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static byte[] readByteArray(long ptr, int off, int len) {
+ byte[] arr0 = new byte[len];
+
+ UNSAFE.copyMemory(null, ptr + off, arr0, BYTE_ARR_OFF, len);
+
+ return arr0;
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @param val Value.
+ */
+ public static void writeBoolean(byte[] arr, int off, boolean val) {
+ writeByte(arr, off, val ? (byte)1 : (byte)0);
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static boolean readBoolean(byte[] arr, int off) {
+ return readByte(arr, off) == 1;
+ }
+
+ /**
+ * @param ptr Pointer.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static boolean readBoolean(long ptr, int off) {
+ return readByte(ptr, off) == 1;
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @param val Value.
+ */
+ public static void writeShort(byte[] arr, int off, short val) {
+ if (BIG_ENDIAN)
+ val = Short.reverseBytes(val);
+
+ UNSAFE.putShort(arr, BYTE_ARR_OFF + off, val);
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static short readShort(byte[] arr, int off) {
+ short val = UNSAFE.getShort(arr, BYTE_ARR_OFF + off);
+
+ if (BIG_ENDIAN)
+ val = Short.reverseBytes(val);
+
+ return val;
+ }
+
+ /**
+ * @param ptr Pointer.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static short readShort(long ptr, int off) {
+ short val = UNSAFE.getShort(ptr + off);
+
+ if (BIG_ENDIAN)
+ val = Short.reverseBytes(val);
+
+ return val;
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @param val Value.
+ */
+ public static void writeChar(byte[] arr, int off, char val) {
+ if (BIG_ENDIAN)
+ val = Character.reverseBytes(val);
+
+ UNSAFE.putChar(arr, BYTE_ARR_OFF + off, val);
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static char readChar(byte[] arr, int off) {
+ char val = UNSAFE.getChar(arr, BYTE_ARR_OFF + off);
+
+ if (BIG_ENDIAN)
+ val = Character.reverseBytes(val);
+
+ return val;
+ }
+
+ /**
+ * @param ptr Pointer.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static char readChar(long ptr, int off) {
+ char val = UNSAFE.getChar(ptr + off);
+
+ if (BIG_ENDIAN)
+ val = Character.reverseBytes(val);
+
+ return val;
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static char[] readCharArray(byte[] arr, int off, int len) {
+ char[] arr0 = new char[len];
+
+ UNSAFE.copyMemory(arr, BYTE_ARR_OFF + off, arr0, CHAR_ARR_OFF, len << 1);
+
+ if (BIG_ENDIAN) {
+ for (int i = 0; i < len; i++)
+ arr0[i] = Character.reverseBytes(arr0[i]);
+ }
+
+ return arr0;
+ }
+
+ /**
+ * @param ptr Pointer.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static char[] readCharArray(long ptr, int off, int len) {
+ char[] arr0 = new char[len];
+
+ UNSAFE.copyMemory(null, ptr + off, arr0, CHAR_ARR_OFF, len << 1);
+
+ if (BIG_ENDIAN) {
+ for (int i = 0; i < len; i++)
+ arr0[i] = Character.reverseBytes(arr0[i]);
+ }
+
+ return arr0;
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @param val Value.
+ */
+ public static void writeInt(byte[] arr, int off, int val) {
+ if (BIG_ENDIAN)
+ val = Integer.reverseBytes(val);
+
+ UNSAFE.putInt(arr, BYTE_ARR_OFF + off, val);
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static int readInt(byte[] arr, int off) {
+ int val = UNSAFE.getInt(arr, BYTE_ARR_OFF + off);
+
+ if (BIG_ENDIAN)
+ val = Integer.reverseBytes(val);
+
+ return val;
+ }
+
+ /**
+ * @param ptr Pointer.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static int readInt(long ptr, int off) {
+ int val = UNSAFE.getInt(ptr + off);
+
+ if (BIG_ENDIAN)
+ val = Integer.reverseBytes(val);
+
+ return val;
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @param val Value.
+ */
+ public static void writeLong(byte[] arr, int off, long val) {
+ if (BIG_ENDIAN)
+ val = Long.reverseBytes(val);
+
+ UNSAFE.putLong(arr, BYTE_ARR_OFF + off, val);
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static long readLong(byte[] arr, int off) {
+ long val = UNSAFE.getLong(arr, BYTE_ARR_OFF + off);
+
+ if (BIG_ENDIAN)
+ val = Long.reverseBytes(val);
+
+ return val;
+ }
+
+ /**
+ * @param ptr Pointer.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static long readLong(long ptr, int off) {
+ long val = UNSAFE.getLong(ptr + off);
+
+ if (BIG_ENDIAN)
+ val = Long.reverseBytes(val);
+
+ return val;
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @param val Value.
+ */
+ public static void writeFloat(byte[] arr, int off, float val) {
+ int val0 = Float.floatToIntBits(val);
+
+ writeInt(arr, off, val0);
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static float readFloat(byte[] arr, int off) {
+ int val = readInt(arr, off);
+
+ return Float.intBitsToFloat(val);
+ }
+
+ /**
+ * @param ptr Pointer.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static float readFloat(long ptr, int off) {
+ int val = readInt(ptr, off);
+
+ return Float.intBitsToFloat(val);
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @param val Value.
+ */
+ public static void writeDouble(byte[] arr, int off, double val) {
+ long val0 = Double.doubleToLongBits(val);
+
+ writeLong(arr, off, val0);
+ }
+
+ /**
+ * @param arr Array.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static double readDouble(byte[] arr, int off) {
+ long val = readLong(arr, off);
+
+ return Double.longBitsToDouble(val);
+ }
+
+ /**
+ * @param ptr Pointer.
+ * @param off Offset.
+ * @return Value.
+ */
+ public static double readDouble(long ptr, int off) {
+ long val = readLong(ptr, off);
+
+ return Double.longBitsToDouble(val);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1dbf20e0/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableSchema.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableSchema.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableSchema.java
new file mode 100644
index 0000000..61b5d45
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableSchema.java
@@ -0,0 +1,466 @@
+/*
+ * 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.binary;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Schema describing portable object content. We rely on the following assumptions:
+ * - When amount of fields in the object is low, it is better to inline these values into int fields thus allowing
+ * for quick comparisons performed within already fetched L1 cache line.
+ * - When there are more fields, we store them inside a hash map.
+ */
+public class PortableSchema implements Externalizable {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** Order returned if field is not found. */
+ public static final int ORDER_NOT_FOUND = -1;
+
+ /** Minimum sensible size. */
+ private static final int MAP_MIN_SIZE = 32;
+
+ /** Empty cell. */
+ private static final int MAP_EMPTY = 0;
+
+ /** Schema ID. */
+ private int schemaId;
+
+ /** IDs depending on order. */
+ private int[] ids;
+
+ /** Interned names of associated fields. */
+ private String[] names;
+
+ /** ID-to-order data. */
+ private int[] idToOrderData;
+
+ /** ID-to-order mask. */
+ private int idToOrderMask;
+
+ /** ID 1. */
+ private int id0;
+
+ /** ID 2. */
+ private int id1;
+
+ /** ID 3. */
+ private int id2;
+
+ /** ID 4. */
+ private int id3;
+
+ /**
+ * {@link Externalizable} support.
+ */
+ public PortableSchema() {
+ // No-op.
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param schemaId Schema ID.
+ * @param fieldIds Field IDs.
+ */
+ private PortableSchema(int schemaId, List<Integer> fieldIds) {
+ assert fieldIds != null;
+
+ this.schemaId = schemaId;
+
+ initialize(fieldIds);
+ }
+
+ /**
+ * @return Schema ID.
+ */
+ public int schemaId() {
+ return schemaId;
+ }
+
+ /**
+ * Try speculatively confirming order for the given field name.
+ *
+ * @param expOrder Expected order.
+ * @param expName Expected name.
+ * @return Field ID.
+ */
+ @SuppressWarnings("StringEquality")
+ public Confirmation confirmOrder(int expOrder, String expName) {
+ assert expName != null;
+
+ if (expOrder < names.length) {
+ String name = names[expOrder];
+
+ // Note that we use only reference equality assuming that field names are interned literals.
+ if (name == expName)
+ return Confirmation.CONFIRMED;
+
+ if (name == null)
+ return Confirmation.CLARIFY;
+ }
+
+ return Confirmation.REJECTED;
+ }
+
+ /**
+ * Add field name.
+ *
+ * @param order Order.
+ * @param name Name.
+ */
+ public void clarifyFieldName(int order, String name) {
+ assert name != null;
+ assert order < names.length;
+
+ names[order] = name.intern();
+ }
+
+ /**
+ * Get field ID by order in footer.
+ *
+ * @param order Order.
+ * @return Field ID.
+ */
+ public int fieldId(int order) {
+ return order < ids.length ? ids[order] : 0;
+ }
+
+ /**
+ * Get field order in footer by field ID.
+ *
+ * @param id Field ID.
+ * @return Offset or {@code 0} if there is no such field.
+ */
+ public int order(int id) {
+ if (idToOrderData == null) {
+ if (id == id0)
+ return 0;
+
+ if (id == id1)
+ return 1;
+
+ if (id == id2)
+ return 2;
+
+ if (id == id3)
+ return 3;
+
+ return ORDER_NOT_FOUND;
+ }
+ else {
+ int idx = (id & idToOrderMask) << 1;
+
+ int curId = idToOrderData[idx];
+
+ if (id == curId) // Hit!
+ return idToOrderData[idx + 1];
+ else if (curId == MAP_EMPTY) // No such ID!
+ return ORDER_NOT_FOUND;
+ else {
+ // Unlikely collision scenario.
+ for (int i = 2; i < idToOrderData.length; i += 2) {
+ int newIdx = (idx + i) % idToOrderData.length;
+
+ assert newIdx < idToOrderData.length - 1;
+
+ curId = idToOrderData[newIdx];
+
+ if (id == curId)
+ return idToOrderData[newIdx + 1];
+ else if (curId == MAP_EMPTY)
+ return ORDER_NOT_FOUND;
+ }
+
+ return ORDER_NOT_FOUND;
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return schemaId;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ return o != null && o instanceof PortableSchema && schemaId == ((PortableSchema)o).schemaId;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeInt(schemaId);
+
+ out.writeInt(ids.length);
+
+ for (Integer id : ids)
+ out.writeInt(id);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ schemaId = in.readInt();
+
+ int idsCnt = in.readInt();
+
+ List<Integer> fieldIds = new ArrayList<>(idsCnt);
+
+ for (int i = 0; i < idsCnt; i++)
+ fieldIds.add(in.readInt());
+
+ initialize(fieldIds);
+ }
+
+ /**
+ * Parse values.
+ *
+ * @param vals Values.
+ * @param size Proposed result size.
+ * @return Parse result.
+ */
+ private static ParseResult parse(int[] vals, int size) {
+ int mask = maskForPowerOfTwo(size);
+
+ int totalSize = size * 2;
+
+ int[] data = new int[totalSize];
+ int collisions = 0;
+
+ for (int order = 0; order < vals.length; order++) {
+ int id = vals[order];
+
+ assert id != 0;
+
+ int idIdx = (id & mask) << 1;
+
+ if (data[idIdx] == 0) {
+ // Found empty slot.
+ data[idIdx] = id;
+ data[idIdx + 1] = order;
+ }
+ else {
+ // Collision!
+ collisions++;
+
+ boolean placeFound = false;
+
+ for (int i = 2; i < totalSize; i += 2) {
+ int newIdIdx = (idIdx + i) % totalSize;
+
+ if (data[newIdIdx] == 0) {
+ data[newIdIdx] = id;
+ data[newIdIdx + 1] = order;
+
+ placeFound = true;
+
+ break;
+ }
+ }
+
+ assert placeFound : "Should always have a place for entry!";
+ }
+ }
+
+ return new ParseResult(data, collisions);
+ }
+
+ /**
+ * Get next power of two which greater or equal to the given number.
+ * This implementation is not meant to be very efficient, so it is expected to be used relatively rare.
+ *
+ * @param val Number
+ * @return Nearest pow2.
+ */
+ private static int nextPowerOfTwo(int val) {
+ int res = 1;
+
+ while (res < val)
+ res = res << 1;
+
+ if (res < 0)
+ throw new IllegalArgumentException("Value is too big to find positive pow2: " + val);
+
+ return res;
+ }
+
+ /**
+ * Calculate mask for the given value which is a power of two.
+ *
+ * @param val Value.
+ * @return Mask.
+ */
+ private static int maskForPowerOfTwo(int val) {
+ int mask = 0;
+ int comparand = 1;
+
+ while (comparand < val) {
+ mask |= comparand;
+
+ comparand <<= 1;
+ }
+
+ return mask;
+ }
+
+ /**
+ * Initialization routine.
+ *
+ * @param fieldIds Field IDs.
+ */
+ private void initialize(List<Integer> fieldIds) {
+ ids = new int[fieldIds.size()];
+
+ for (int i = 0; i < fieldIds.size(); i++)
+ ids[i] = fieldIds.get(i);
+
+ names = new String[fieldIds.size()];
+
+ if (fieldIds.size() <= 4) {
+ Iterator<Integer> iter = fieldIds.iterator();
+
+ id0 = iter.hasNext() ? iter.next() : 0;
+ id1 = iter.hasNext() ? iter.next() : 0;
+ id2 = iter.hasNext() ? iter.next() : 0;
+ id3 = iter.hasNext() ? iter.next() : 0;
+ }
+ else {
+ id0 = id1 = id2 = id3 = 0;
+
+ initializeMap(ids);
+ }
+ }
+
+ /**
+ * Initialize the map.
+ *
+ * @param vals Values.
+ */
+ private void initializeMap(int[] vals) {
+ int size = Math.max(nextPowerOfTwo(vals.length) << 2, MAP_MIN_SIZE);
+
+ assert size > 0;
+
+ ParseResult finalRes;
+
+ ParseResult res1 = parse(vals, size);
+
+ if (res1.collisions == 0)
+ finalRes = res1;
+ else {
+ ParseResult res2 = parse(vals, size * 2);
+
+ // Failed to decrease aom
+ if (res2.collisions == 0)
+ finalRes = res2;
+ else
+ finalRes = parse(vals, size * 4);
+ }
+
+ idToOrderData = finalRes.data;
+ idToOrderMask = maskForPowerOfTwo(idToOrderData.length / 2);
+ }
+
+ /**
+ * Schema builder.
+ */
+ public static class Builder {
+ /** Schema ID. */
+ private int schemaId = PortableUtils.schemaInitialId();
+
+ /** Fields. */
+ private final ArrayList<Integer> fields = new ArrayList<>();
+
+ /**
+ * Create new schema builder.
+ *
+ * @return Schema builder.
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Private constructor.
+ */
+ private Builder() {
+ // No-op.
+ }
+
+ /**
+ * Add field.
+ *
+ * @param fieldId Field ID.
+ */
+ public void addField(int fieldId) {
+ fields.add(fieldId);
+
+ schemaId = PortableUtils.updateSchemaId(schemaId, fieldId);
+ }
+
+ /**
+ * Build schema.
+ *
+ * @return Schema.
+ */
+ public PortableSchema build() {
+ return new PortableSchema(schemaId, fields);
+ }
+ }
+
+ /**
+ * Order confirmation result.
+ */
+ public enum Confirmation {
+ /** Confirmed. */
+ CONFIRMED,
+
+ /** Denied. */
+ REJECTED,
+
+ /** Field name clarification is needed. */
+ CLARIFY
+ }
+
+ /**
+ * Result of map parsing.
+ */
+ private static class ParseResult {
+ /** Data. */
+ private int[] data;
+
+ /** Collisions. */
+ private int collisions;
+
+ /**
+ * Constructor.
+ *
+ * @param data Data.
+ * @param collisions Collisions.
+ */
+ private ParseResult(int[] data, int collisions) {
+ this.data = data;
+ this.collisions = collisions;
+ }
+ }
+}