blob: 508ef01c6294e397603e1706851c666eb52828c0 [file] [log] [blame]
/*
* 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.marshaller.optimized;
import java.io.Externalizable;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import org.apache.ignite.internal.binary.GridBinaryMarshaller;
import org.apache.ignite.internal.util.GridUnsafe;
import org.apache.ignite.internal.util.SerializableTransient;
import org.apache.ignite.internal.util.TransientSerializable;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteProductVersion;
import org.apache.ignite.marshaller.MarshallerContext;
import org.apache.ignite.marshaller.MarshallerExclusions;
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPrivate;
import static java.lang.reflect.Modifier.isStatic;
import static java.lang.reflect.Modifier.isTransient;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.ARRAY_LIST;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.BOOLEAN;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.BOOLEAN_ARR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.BYTE;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.BYTE_ARR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.CHAR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.CHAR_ARR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.CLS;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.DATE;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.DOUBLE;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.DOUBLE_ARR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.ENUM;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.EXTERNALIZABLE;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.FLOAT;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.FLOAT_ARR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.HASH_MAP;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.HASH_SET;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.HASH_SET_MAP_OFF;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.INT;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.INT_ARR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.LINKED_HASH_MAP;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.LINKED_HASH_SET;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.LINKED_LIST;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.LONG;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.LONG_ARR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.OBJ_ARR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.PROPS;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.PROXY;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.SERIALIZABLE;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.SHORT;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.SHORT_ARR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.STR;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.UUID;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.computeSerialVersionUid;
import static org.apache.ignite.marshaller.MarshallerUtils.jobReceiverVersion;
import static org.apache.ignite.marshaller.MarshallerUtils.jobSenderVersion;
/**
* Class descriptor.
*/
class OptimizedClassDescriptor {
/** Class. */
private final Class<?> cls;
/** Context. */
private final MarshallerContext ctx;
/** */
private ConcurrentMap<Class, OptimizedClassDescriptor> clsMap;
/** ID mapper. */
private final OptimizedMarshallerIdMapper mapper;
/** Class name. */
private final String name;
/** Type ID. */
private final int typeId;
/** Short ID. */
private final short checksum;
/** Class type. */
private int type;
/** Primitive flag. */
private boolean isPrimitive;
/** Enum flag. */
private boolean isEnum;
/** Serializable flag. */
private boolean isSerial;
/** Excluded flag. */
private boolean excluded;
/** {@code True} if descriptor is for {@link Class}. */
private boolean isCls;
/** Enumeration values. */
private Object[] enumVals;
/** Constructor. */
private Constructor<?> constructor;
/** Fields. */
private Fields fields;
/** {@code writeObject} methods. */
private List<Method> writeObjMtds;
/** {@code writeReplace} method. */
private Method writeReplaceMtd;
/** {@code readObject} methods. */
private List<Method> readObjMtds;
/** {@code readResolve} method. */
private Method readResolveMtd;
/** Defaults field offset. */
private long dfltsFieldOff;
/** Load factor field offset. */
private long loadFactorFieldOff;
/** Access order field offset. */
private long accessOrderFieldOff;
/** Proxy interfaces. */
private Class<?>[] proxyIntfs;
/** Method returns serializable transient fields. */
private Method serTransMtd;
/** Method returns transient serializable fields. */
private Method transSerMtd;
/**
* Creates descriptor for class.
*
* @param typeId Type ID.
* @param clsMap Class descriptors by class map.
* @param cls Class.
* @param ctx Context.
* @param mapper ID mapper.
* @throws IOException In case of error.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
OptimizedClassDescriptor(Class<?> cls,
int typeId,
ConcurrentMap<Class, OptimizedClassDescriptor> clsMap,
MarshallerContext ctx,
OptimizedMarshallerIdMapper mapper)
throws IOException {
this(
cls,
typeId,
clsMap,
ctx,
mapper,
MarshallerExclusions.isExcluded(cls)
);
}
/**
* Creates descriptor for class.
*
* @param typeId Type ID.
* @param clsMap Class descriptors by class map.
* @param cls Class.
* @param ctx Context.
* @param mapper ID mapper.
* @throws IOException In case of error.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
OptimizedClassDescriptor(Class<?> cls,
int typeId,
ConcurrentMap<Class, OptimizedClassDescriptor> clsMap,
MarshallerContext ctx,
OptimizedMarshallerIdMapper mapper,
boolean excluded)
throws IOException {
this.cls = cls;
this.typeId = typeId;
this.clsMap = clsMap;
this.ctx = ctx;
this.mapper = mapper;
name = cls.getName();
this.excluded = excluded;
if (!excluded) {
Class<?> parent;
if (cls == byte.class || cls == Byte.class) {
type = BYTE;
isPrimitive = true;
}
else if (cls == short.class || cls == Short.class) {
type = SHORT;
isPrimitive = true;
}
else if (cls == int.class || cls == Integer.class) {
type = INT;
isPrimitive = true;
}
else if (cls == long.class || cls == Long.class) {
type = LONG;
isPrimitive = true;
}
else if (cls == float.class || cls == Float.class) {
type = FLOAT;
isPrimitive = true;
}
else if (cls == double.class || cls == Double.class) {
type = DOUBLE;
isPrimitive = true;
}
else if (cls == char.class || cls == Character.class) {
type = CHAR;
isPrimitive = true;
}
else if (cls == boolean.class || cls == Boolean.class) {
type = BOOLEAN;
isPrimitive = true;
}
else if (cls == byte[].class)
type = BYTE_ARR;
else if (cls == short[].class)
type = SHORT_ARR;
else if (cls == int[].class)
type = INT_ARR;
else if (cls == long[].class)
type = LONG_ARR;
else if (cls == float[].class)
type = FLOAT_ARR;
else if (cls == double[].class)
type = DOUBLE_ARR;
else if (cls == char[].class)
type = CHAR_ARR;
else if (cls == boolean[].class)
type = BOOLEAN_ARR;
else if (cls.isArray())
type = OBJ_ARR;
else if (cls == String.class)
type = STR;
else if (cls.isEnum()) {
type = ENUM;
isEnum = true;
enumVals = cls.getEnumConstants();
}
// Support for enum constants, based on anonymous children classes.
else if ((parent = cls.getSuperclass()) != null && parent.isEnum()) {
type = ENUM;
isEnum = true;
enumVals = parent.getEnumConstants();
}
else if (cls == UUID.class)
type = UUID;
else if (cls == Properties.class) {
type = PROPS;
try {
dfltsFieldOff = GridUnsafe.objectFieldOffset(Properties.class.getDeclaredField("defaults"));
}
catch (NoSuchFieldException e) {
throw new IOException(e);
}
}
else if (cls == ArrayList.class)
type = ARRAY_LIST;
else if (cls == HashMap.class) {
type = HASH_MAP;
try {
loadFactorFieldOff = GridUnsafe.objectFieldOffset(HashMap.class.getDeclaredField("loadFactor"));
}
catch (NoSuchFieldException e) {
throw new IOException(e);
}
}
else if (cls == HashSet.class) {
type = HASH_SET;
try {
loadFactorFieldOff = GridUnsafe.objectFieldOffset(HashMap.class.getDeclaredField("loadFactor"));
}
catch (NoSuchFieldException e) {
throw new IOException(e);
}
}
else if (cls == LinkedList.class)
type = LINKED_LIST;
else if (cls == LinkedHashMap.class) {
type = LINKED_HASH_MAP;
try {
loadFactorFieldOff =
GridUnsafe.objectFieldOffset(HashMap.class.getDeclaredField("loadFactor"));
accessOrderFieldOff =
GridUnsafe.objectFieldOffset(LinkedHashMap.class.getDeclaredField("accessOrder"));
}
catch (NoSuchFieldException e) {
throw new IOException(e);
}
}
else if (cls == LinkedHashSet.class) {
type = LINKED_HASH_SET;
try {
loadFactorFieldOff = GridUnsafe.objectFieldOffset(HashMap.class.getDeclaredField("loadFactor"));
}
catch (NoSuchFieldException e) {
throw new IOException(e);
}
}
else if (cls == Date.class)
type = DATE;
else if (cls == Class.class) {
type = CLS;
isCls = true;
}
else if (Proxy.class.isAssignableFrom(cls)) {
type = PROXY;
proxyIntfs = cls.getInterfaces();
}
else {
Class<?> c = cls;
while ((writeReplaceMtd == null || readResolveMtd == null) && c != null && !c.equals(Object.class)) {
if (writeReplaceMtd == null) {
try {
writeReplaceMtd = c.getDeclaredMethod("writeReplace");
if (!isStatic(writeReplaceMtd.getModifiers()) &&
!(isPrivate(writeReplaceMtd.getModifiers()) && c != cls) &&
writeReplaceMtd.getReturnType().equals(Object.class))
writeReplaceMtd.setAccessible(true);
else
// Set method back to null if it has incorrect signature.
writeReplaceMtd = null;
}
catch (NoSuchMethodException ignored) {
// No-op.
}
}
if (readResolveMtd == null) {
try {
readResolveMtd = c.getDeclaredMethod("readResolve");
if (!isStatic(readResolveMtd.getModifiers()) &&
!(isPrivate(readResolveMtd.getModifiers()) && c != cls) &&
readResolveMtd.getReturnType().equals(Object.class))
readResolveMtd.setAccessible(true);
else
// Set method back to null if it has incorrect signature.
readResolveMtd = null;
}
catch (NoSuchMethodException ignored) {
// No-op.
}
}
c = c.getSuperclass();
}
if (Externalizable.class.isAssignableFrom(cls)) {
type = EXTERNALIZABLE;
try {
constructor = !Modifier.isStatic(cls.getModifiers()) && cls.getDeclaringClass() != null ?
cls.getDeclaredConstructor(cls.getDeclaringClass()) :
cls.getDeclaredConstructor();
constructor.setAccessible(true);
}
catch (NoSuchMethodException e) {
throw new IOException("Externalizable class doesn't have default constructor: " + cls, e);
}
}
else {
type = SERIALIZABLE;
isSerial = Serializable.class.isAssignableFrom(cls);
writeObjMtds = new ArrayList<>();
readObjMtds = new ArrayList<>();
List<ClassFields> fields = new ArrayList<>();
for (c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) {
Method mtd;
try {
mtd = c.getDeclaredMethod("writeObject", ObjectOutputStream.class);
int mod = mtd.getModifiers();
if (!isStatic(mod) && isPrivate(mod) && mtd.getReturnType() == Void.TYPE)
mtd.setAccessible(true);
else
// Set method back to null if it has incorrect signature.
mtd = null;
}
catch (NoSuchMethodException ignored) {
mtd = null;
}
writeObjMtds.add(mtd);
try {
mtd = c.getDeclaredMethod("readObject", ObjectInputStream.class);
int mod = mtd.getModifiers();
if (!isStatic(mod) && isPrivate(mod) && mtd.getReturnType() == Void.TYPE)
mtd.setAccessible(true);
else
// Set method back to null if it has incorrect signature.
mtd = null;
}
catch (NoSuchMethodException ignored) {
mtd = null;
}
readObjMtds.add(mtd);
final SerializableTransient serTransAn = c.getAnnotation(SerializableTransient.class);
final TransientSerializable transSerAn = c.getAnnotation(TransientSerializable.class);
// Custom serialization policy for transient fields.
if (serTransAn != null) {
try {
serTransMtd = c.getDeclaredMethod(serTransAn.methodName(), IgniteProductVersion.class);
int mod = serTransMtd.getModifiers();
if (isStatic(mod) && isPrivate(mod) && serTransMtd.getReturnType() == String[].class)
serTransMtd.setAccessible(true);
else
// Set method back to null if it has incorrect signature.
serTransMtd = null;
}
catch (NoSuchMethodException ignored) {
serTransMtd = null;
}
}
// Custom serialization policy for non-transient fields.
if (transSerAn != null) {
try {
transSerMtd = c.getDeclaredMethod(transSerAn.methodName(), IgniteProductVersion.class);
int mod = transSerMtd.getModifiers();
if (isStatic(mod) && isPrivate(mod) && transSerMtd.getReturnType() == String[].class)
transSerMtd.setAccessible(true);
else
// Set method back to null if it has incorrect signature.
transSerMtd = null;
}
catch (NoSuchMethodException ignored) {
transSerMtd = null;
}
}
Field[] clsFields0 = c.getDeclaredFields();
Map<String, Field> fieldNames = new HashMap<>();
for (Field f : clsFields0)
fieldNames.put(f.getName(), f);
List<FieldInfo> clsFields = new ArrayList<>(clsFields0.length);
boolean hasSerialPersistentFields = false;
try {
Field serFieldsDesc = c.getDeclaredField("serialPersistentFields");
int mod = serFieldsDesc.getModifiers();
if (serFieldsDesc.getType() == ObjectStreamField[].class &&
isPrivate(mod) && isStatic(mod) && isFinal(mod)) {
hasSerialPersistentFields = true;
serFieldsDesc.setAccessible(true);
ObjectStreamField[] serFields = (ObjectStreamField[]) serFieldsDesc.get(null);
for (int i = 0; i < serFields.length; i++) {
ObjectStreamField serField = serFields[i];
FieldInfo fieldInfo;
if (!fieldNames.containsKey(serField.getName())) {
fieldInfo = new FieldInfo(null,
serField.getName(),
-1,
fieldType(serField.getType()));
}
else {
Field f = fieldNames.get(serField.getName());
fieldInfo = new FieldInfo(f,
serField.getName(),
GridUnsafe.objectFieldOffset(f),
fieldType(serField.getType()));
}
clsFields.add(fieldInfo);
}
}
}
catch (NoSuchFieldException ignored) {
// No-op.
}
catch (IllegalAccessException e) {
throw new IOException("Failed to get value of 'serialPersistentFields' field in class: " +
cls.getName(), e);
}
if (!hasSerialPersistentFields) {
for (int i = 0; i < clsFields0.length; i++) {
Field f = clsFields0[i];
int mod = f.getModifiers();
if (!isStatic(mod) && !isTransient(mod)) {
FieldInfo fieldInfo = new FieldInfo(f, f.getName(),
GridUnsafe.objectFieldOffset(f), fieldType(f.getType()));
clsFields.add(fieldInfo);
}
}
}
Collections.sort(clsFields, new Comparator<FieldInfo>() {
@Override public int compare(FieldInfo t1, FieldInfo t2) {
return t1.name().compareTo(t2.name());
}
});
fields.add(new ClassFields(clsFields));
}
Collections.reverse(writeObjMtds);
Collections.reverse(readObjMtds);
Collections.reverse(fields);
this.fields = new Fields(fields);
}
}
}
checksum = computeSerialVersionUid(cls, fields != null ? fields.ownFields() : null);
}
/**
* @return Excluded flag.
*/
boolean excluded() {
return excluded;
}
/**
* @return Class.
*/
Class<?> describedClass() {
return cls;
}
/**
* @return Primitive flag.
*/
boolean isPrimitive() {
return isPrimitive;
}
/**
* @return Enum flag.
*/
boolean isEnum() {
return isEnum;
}
/**
* @return {@code True} if descriptor is for {@link Class}.
*/
boolean isClass() {
return isCls;
}
/**
* @return {@code True} if descriptor is for {@link Proxy}.
*/
boolean isProxy() {
return type == PROXY;
}
/**
* Replaces object.
*
* @param obj Object.
* @return Replaced object or {@code null} if there is no {@code writeReplace} method.
* @throws IOException In case of error.
*/
Object replace(Object obj) throws IOException {
if (writeReplaceMtd != null) {
try {
return writeReplaceMtd.invoke(obj);
}
catch (IllegalAccessException | InvocationTargetException e) {
throw new IOException(e);
}
}
return obj;
}
/**
* Writes object to stream.
*
* @param out Output stream.
* @param obj Object.
* @throws IOException In case of error.
*/
void write(OptimizedObjectOutputStream out, Object obj) throws IOException {
out.write(type);
switch (type) {
case BYTE:
out.writeByte((Byte)obj);
break;
case SHORT:
out.writeShort((Short)obj);
break;
case INT:
out.writeInt((Integer)obj);
break;
case LONG:
out.writeLong((Long)obj);
break;
case FLOAT:
out.writeFloat((Float)obj);
break;
case DOUBLE:
out.writeDouble((Double)obj);
break;
case CHAR:
out.writeChar((Character)obj);
break;
case BOOLEAN:
out.writeBoolean((Boolean)obj);
break;
case BYTE_ARR:
out.writeByteArray((byte[])obj);
break;
case SHORT_ARR:
out.writeShortArray((short[])obj);
break;
case INT_ARR:
out.writeIntArray((int[])obj);
break;
case LONG_ARR:
out.writeLongArray((long[])obj);
break;
case FLOAT_ARR:
out.writeFloatArray((float[])obj);
break;
case DOUBLE_ARR:
out.writeDoubleArray((double[])obj);
break;
case CHAR_ARR:
out.writeCharArray((char[])obj);
break;
case BOOLEAN_ARR:
out.writeBooleanArray((boolean[])obj);
break;
case OBJ_ARR:
OptimizedClassDescriptor compDesc = OptimizedMarshallerUtils.classDescriptor(clsMap,
obj.getClass().getComponentType(),
GridBinaryMarshaller.USE_CACHE.get(),
ctx,
mapper);
compDesc.writeTypeData(out);
out.writeArray((Object[])obj);
break;
case STR:
out.writeString((String)obj);
break;
case UUID:
out.writeUuid((UUID)obj);
break;
case PROPS:
out.writeProperties((Properties)obj, dfltsFieldOff);
break;
case ARRAY_LIST:
out.writeArrayList((ArrayList<?>)obj);
break;
case HASH_MAP:
out.writeHashMap((HashMap<?, ?>)obj, loadFactorFieldOff, false);
break;
case HASH_SET:
out.writeHashSet((HashSet<?>)obj, HASH_SET_MAP_OFF, loadFactorFieldOff);
break;
case LINKED_LIST:
out.writeLinkedList((LinkedList<?>)obj);
break;
case LINKED_HASH_MAP:
out.writeLinkedHashMap((LinkedHashMap<?, ?>)obj, loadFactorFieldOff, accessOrderFieldOff, false);
break;
case LINKED_HASH_SET:
out.writeLinkedHashSet((LinkedHashSet<?>)obj, HASH_SET_MAP_OFF, loadFactorFieldOff);
break;
case DATE:
out.writeDate((Date)obj);
break;
case CLS:
OptimizedClassDescriptor clsDesc = OptimizedMarshallerUtils.classDescriptor(
clsMap, (Class<?>)obj, GridBinaryMarshaller.USE_CACHE.get(), ctx, mapper);
clsDesc.writeTypeData(out);
break;
case PROXY:
out.writeInt(proxyIntfs.length);
for (Class<?> intf : proxyIntfs) {
OptimizedClassDescriptor intfDesc = OptimizedMarshallerUtils.classDescriptor(
clsMap, intf, GridBinaryMarshaller.USE_CACHE.get(), ctx, mapper);
intfDesc.writeTypeData(out);
}
InvocationHandler ih = Proxy.getInvocationHandler(obj);
assert ih != null;
out.writeObject(ih);
break;
case ENUM:
writeTypeData(out);
out.writeInt(((Enum)obj).ordinal());
break;
case EXTERNALIZABLE:
writeTypeData(out);
out.writeShort(checksum);
out.writeExternalizable(obj);
break;
case SERIALIZABLE:
if (out.requireSerializable() && !isSerial)
throw new NotSerializableException("Must implement java.io.Serializable or " +
"set OptimizedMarshaller.setRequireSerializable() to false " +
"(note that performance may degrade if object is not Serializable): " + name);
writeTypeData(out);
out.writeShort(checksum);
out.writeSerializable(obj, writeObjMtds, fields(obj.getClass(), jobReceiverVersion()));
break;
default:
throw new IllegalStateException("Invalid class type: " + type);
}
}
/**
* Gets list of serializable fields. If {@link #serTransMtd} method
* returns list of transient fields, they will be added to other fields.
* Transient fields that are not included in that list will be normally
* ignored.
*
* @param cls Class.
* @param ver Job sender version.
* @return Serializable fields.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
private Fields fields(Class<?> cls, IgniteProductVersion ver) {
if (ver == null // No context available.
|| serTransMtd == null && transSerMtd == null)
return fields;
try {
final String[] transFields = serTransMtd == null ? null : (String[])serTransMtd.invoke(null, ver);
final String[] serFields = transSerMtd == null ? null : (String[])transSerMtd.invoke(null, ver);
if (F.isEmpty(transFields) && F.isEmpty(serFields))
return fields;
Map<String, FieldInfo> clsFields = new TreeMap<>();
for (FieldInfo field : fields.fields.get(0).fields) {
clsFields.put(field.fieldName, field);
}
// Add serializable transient fields
if (!F.isEmpty(transFields)) {
for (int i = 0; i < transFields.length; i++) {
final String fieldName = transFields[i];
final Field f = cls.getDeclaredField(fieldName);
FieldInfo fieldInfo = new FieldInfo(f, f.getName(),
GridUnsafe.objectFieldOffset(f), fieldType(f.getType()));
clsFields.put(fieldName, fieldInfo);
}
}
// Exclude non-transient fields which shouldn't be serialized.
if (!F.isEmpty(serFields)) {
for (int i = 0; i < serFields.length; i++) {
clsFields.remove(serFields[i]);
}
}
List<ClassFields> fields = new ArrayList<>(1);
fields.add(new ClassFields(new ArrayList<>(clsFields.values())));
return new Fields(fields);
}
catch (Exception ignored) {
return fields;
}
}
/**
* @param out Output stream.
* @throws IOException In case of error.
*/
void writeTypeData(OptimizedObjectOutputStream out) throws IOException {
out.writeInt(typeId);
if (typeId == 0)
out.writeUTF(name);
}
/**
* Reads object from stream.
*
* @param in Input stream.
* @return Object.
* @throws ClassNotFoundException If class not found.
* @throws IOException In case of error.
*/
Object read(OptimizedObjectInputStream in) throws ClassNotFoundException, IOException {
switch (type) {
case ENUM:
return enumVals[in.readInt()];
case EXTERNALIZABLE:
verifyChecksum(in.readShort());
return in.readExternalizable(constructor, readResolveMtd);
case SERIALIZABLE:
verifyChecksum(in.readShort());
return in.readSerializable(cls, readObjMtds, readResolveMtd, fields(cls, jobSenderVersion()));
default:
assert false : "Unexpected type: " + type;
return null;
}
}
/**
* @param checksum Checksum.
* @throws ClassNotFoundException If checksum is wrong.
* @throws IOException In case of error.
*/
private void verifyChecksum(short checksum) throws ClassNotFoundException, IOException {
if (checksum != this.checksum)
throw new ClassNotFoundException("Optimized stream class checksum mismatch " +
"(is same version of marshalled class present on all nodes?) " +
"[expected=" + this.checksum + ", actual=" + checksum + ", cls=" + cls + ']');
}
/**
* @param cls Class.
* @return Type.
*/
private OptimizedFieldType fieldType(Class<?> cls) {
OptimizedFieldType type;
if (cls == byte.class)
type = OptimizedFieldType.BYTE;
else if (cls == short.class)
type = OptimizedFieldType.SHORT;
else if (cls == int.class)
type = OptimizedFieldType.INT;
else if (cls == long.class)
type = OptimizedFieldType.LONG;
else if (cls == float.class)
type = OptimizedFieldType.FLOAT;
else if (cls == double.class)
type = OptimizedFieldType.DOUBLE;
else if (cls == char.class)
type = OptimizedFieldType.CHAR;
else if (cls == boolean.class)
type = OptimizedFieldType.BOOLEAN;
else
type = OptimizedFieldType.OTHER;
return type;
}
/**
* Information about one field.
*/
@SuppressWarnings("PackageVisibleInnerClass")
static class FieldInfo {
/** Field. */
private final Field field;
/** Field offset. */
private final long fieldOffs;
/** Field type. */
private final OptimizedFieldType fieldType;
/** Field name. */
private final String fieldName;
/**
* @param field Field.
* @param name Field name.
* @param offset Field offset.
* @param type Grid optimized field type.
*/
FieldInfo(Field field, String name, long offset, OptimizedFieldType type) {
this.field = field;
fieldOffs = offset;
fieldType = type;
fieldName = name;
}
/**
* @return Returns field.
*/
Field field() {
return field;
}
/**
* @return Offset.
*/
long offset() {
return fieldOffs;
}
/**
* @return Type.
*/
OptimizedFieldType type() {
return fieldType;
}
/**
* @return Name.
*/
String name() {
return fieldName;
}
}
/**
* Information about one class.
*/
static class ClassFields {
/** Fields. */
private final List<FieldInfo> fields;
/** */
private final Map<String, Integer> nameToIndex;
/**
* @param fields Field infos.
*/
ClassFields(List<FieldInfo> fields) {
this.fields = fields;
nameToIndex = U.newHashMap(fields.size());
for (int i = 0; i < fields.size(); ++i)
nameToIndex.put(fields.get(i).name(), i);
}
/**
* @return Class fields.
*/
List<FieldInfo> fields() {
return fields;
}
/**
* @return Fields count.
*/
int size() {
return fields.size();
}
/**
* @param i Field's index.
* @return FieldInfo.
*/
FieldInfo get(int i) {
return fields.get(i);
}
/**
* @param name Field's name.
* @return Field's index.
*/
int getIndex(String name) {
assert nameToIndex.containsKey(name);
return nameToIndex.get(name);
}
}
/**
* Encapsulates data about class fields.
*/
@SuppressWarnings("PackageVisibleInnerClass")
static class Fields {
/** Fields. */
private final List<ClassFields> fields;
/** Own fields (excluding inherited). */
private final List<Field> ownFields;
/**
* Creates new instance.
*
* @param fields Fields.
*/
Fields(List<ClassFields> fields) {
this.fields = fields;
if (fields.isEmpty())
ownFields = null;
else {
ownFields = new ArrayList<>(fields.size());
for (FieldInfo f : fields.get(fields.size() - 1).fields()) {
if (f.field() != null)
ownFields.add(f.field);
}
}
}
/**
* Returns class's own fields (excluding inherited).
*
* @return List of fields or {@code null} if fields list is empty.
*/
List<Field> ownFields() {
return ownFields;
}
/**
* Returns field types and their offsets.
*
* @param i hierarchy level where 0 corresponds to top level.
* @return list of pairs where first value is field type and second value is its offset.
*/
ClassFields fields(int i) {
return fields.get(i);
}
}
}