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;
+        }
+    }
+}