http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/5674cdde/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedClassDescriptor.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedClassDescriptor.java b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedClassDescriptor.java new file mode 100644 index 0000000..0f746f8 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedClassDescriptor.java @@ -0,0 +1,1040 @@ +/* + * 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.marshaller.optimized; + +import org.apache.ignite.internal.util.*; +import org.apache.ignite.internal.util.typedef.*; +import org.apache.ignite.lang.*; +import org.apache.ignite.marshaller.*; +import sun.misc.*; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import static java.lang.reflect.Modifier.*; + +/** + * Class descriptor. + */ +class OptimizedClassDescriptor { + /** Unsafe. */ + private static final Unsafe UNSAFE = GridUnsafe.unsafe(); + + /** */ + private static final int TYPE_BYTE = 1; + + /** */ + private static final int TYPE_SHORT = 2; + + /** */ + private static final int TYPE_INT = 3; + + /** */ + private static final int TYPE_LONG = 4; + + /** */ + private static final int TYPE_FLOAT = 5; + + /** */ + private static final int TYPE_DOUBLE = 6; + + /** */ + private static final int TYPE_CHAR = 7; + + /** */ + private static final int TYPE_BOOLEAN = 8; + + /** */ + private static final int TYPE_BYTE_ARR = 9; + + /** */ + private static final int TYPE_SHORT_ARR = 10; + + /** */ + private static final int TYPE_INT_ARR = 11; + + /** */ + private static final int TYPE_LONG_ARR = 12; + + /** */ + private static final int TYPE_FLOAT_ARR = 13; + + /** */ + private static final int TYPE_DOUBLE_ARR = 14; + + /** */ + private static final int TYPE_CHAR_ARR = 15; + + /** */ + private static final int TYPE_BOOLEAN_ARR = 16; + + /** */ + private static final int TYPE_OBJ_ARR = 17; + + /** */ + private static final int TYPE_STR = 18; + + /** */ + private static final int TYPE_ENUM = 19; + + /** */ + private static final int TYPE_UUID = 20; + + /** */ + private static final int TYPE_PROPS = 21; + + /** */ + private static final int TYPE_ARRAY_LIST = 22; + + /** */ + private static final int TYPE_HASH_MAP = 23; + + /** */ + private static final int TYPE_HASH_SET = 24; + + /** */ + private static final int TYPE_LINKED_LIST = 25; + + /** */ + private static final int TYPE_LINKED_HASH_MAP = 26; + + /** */ + private static final int TYPE_LINKED_HASH_SET = 27; + + /** */ + private static final int TYPE_DATE = 28; + + /** */ + private static final int TYPE_CLS = 29; + + /** */ + private static final int TYPE_EXTERNALIZABLE = 50; + + /** */ + private static final int TYPE_SERIALIZABLE = 51; + + /** Class. */ + private Class<?> cls; + + /** Header. */ + private Integer hdr; + + /** ID. */ + private Integer id; + + /** Short ID. */ + private Short shortId; + + /** Class name. */ + private String name; + + /** Class type. */ + private int type; + + /** Primitive flag. */ + private boolean isPrimitive; + + /** Enum flag. */ + private boolean isEnum; + + /** Serializable flag. */ + private boolean isSerial; + + /** Excluded flag. */ + private final boolean excluded; + + /** {@code True} if descriptor is for {@link Class}. */ + private boolean isCls; + + /** Array component type. */ + private Class<?> arrCompType; + + /** 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; + + /** Map field offset. */ + private long mapFieldOff; + + /** Access order field offset. */ + private long accessOrderFieldOff; + + /** + * Creates descriptor for class. + * + * @param cls Class. + * @throws IOException In case of error. + */ + @SuppressWarnings({"ForLoopReplaceableByForEach", "MapReplaceableByEnumMap"}) + OptimizedClassDescriptor(Class<?> cls) throws IOException { + this.cls = cls; + + excluded = MarshallerExclusions.isExcluded(cls); + + T2<Integer, Integer> t = OptimizedClassResolver.writeClassData(cls); + + hdr = t.get1(); + id = t.get2(); + name = cls.getName(); + + if (!excluded) { + Class<?> parent; + + if (cls == byte.class || cls == Byte.class) { + type = TYPE_BYTE; + + isPrimitive = true; + } + else if (cls == short.class || cls == Short.class) { + type = TYPE_SHORT; + + isPrimitive = true; + } + else if (cls == int.class || cls == Integer.class) { + type = TYPE_INT; + + isPrimitive = true; + } + else if (cls == long.class || cls == Long.class) { + type = TYPE_LONG; + + isPrimitive = true; + } + else if (cls == float.class || cls == Float.class) { + type = TYPE_FLOAT; + + isPrimitive = true; + } + else if (cls == double.class || cls == Double.class) { + type = TYPE_DOUBLE; + + isPrimitive = true; + } + else if (cls == char.class || cls == Character.class) { + type = TYPE_CHAR; + + isPrimitive = true; + } + else if (cls == boolean.class || cls == Boolean.class) { + type = TYPE_BOOLEAN; + + isPrimitive = true; + } + else if (cls == byte[].class) + type = TYPE_BYTE_ARR; + else if (cls == short[].class) + type = TYPE_SHORT_ARR; + else if (cls == int[].class) + type = TYPE_INT_ARR; + else if (cls == long[].class) + type = TYPE_LONG_ARR; + else if (cls == float[].class) + type = TYPE_FLOAT_ARR; + else if (cls == double[].class) + type = TYPE_DOUBLE_ARR; + else if (cls == char[].class) + type = TYPE_CHAR_ARR; + else if (cls == boolean[].class) + type = TYPE_BOOLEAN_ARR; + else if (cls.isArray()) { + type = TYPE_OBJ_ARR; + + arrCompType = cls.getComponentType(); + } + else if (cls == String.class) + type = TYPE_STR; + else if (cls.isEnum()) { + type = 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 = TYPE_ENUM; + + isEnum = true; + enumVals = parent.getEnumConstants(); + } + else if (cls == UUID.class) + type = TYPE_UUID; + else if (cls == Properties.class) { + type = TYPE_PROPS; + + try { + dfltsFieldOff = UNSAFE.objectFieldOffset(Properties.class.getDeclaredField("defaults")); + } + catch (NoSuchFieldException e) { + throw new IOException(e); + } + } + else if (cls == ArrayList.class) + type = TYPE_ARRAY_LIST; + else if (cls == HashMap.class) { + type = TYPE_HASH_MAP; + + try { + loadFactorFieldOff = UNSAFE.objectFieldOffset(HashMap.class.getDeclaredField("loadFactor")); + } + catch (NoSuchFieldException e) { + throw new IOException(e); + } + } + else if (cls == HashSet.class) { + type = TYPE_HASH_SET; + + try { + loadFactorFieldOff = UNSAFE.objectFieldOffset(HashMap.class.getDeclaredField("loadFactor")); + mapFieldOff = UNSAFE.objectFieldOffset(HashSet.class.getDeclaredField("map")); + } + catch (NoSuchFieldException e) { + throw new IOException(e); + } + } + else if (cls == LinkedList.class) + type = TYPE_LINKED_LIST; + else if (cls == LinkedHashMap.class) { + type = TYPE_LINKED_HASH_MAP; + + try { + loadFactorFieldOff = UNSAFE.objectFieldOffset(HashMap.class.getDeclaredField("loadFactor")); + accessOrderFieldOff = UNSAFE.objectFieldOffset(LinkedHashMap.class.getDeclaredField("accessOrder")); + } + catch (NoSuchFieldException e) { + throw new IOException(e); + } + } + else if (cls == LinkedHashSet.class) { + type = TYPE_LINKED_HASH_SET; + + try { + loadFactorFieldOff = UNSAFE.objectFieldOffset(HashMap.class.getDeclaredField("loadFactor")); + mapFieldOff = UNSAFE.objectFieldOffset(HashSet.class.getDeclaredField("map")); + } + catch (NoSuchFieldException e) { + throw new IOException(e); + } + } + else if (cls == Date.class) + type = TYPE_DATE; + else if (cls == Class.class) { + type = TYPE_CLS; + + isCls = true; + } + 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 = TYPE_EXTERNALIZABLE; + + try { + constructor = cls.getDeclaredConstructor(); + + constructor.setAccessible(true); + } + catch (NoSuchMethodException e) { + throw new IOException("Externalizable class doesn't have default constructor: " + cls, e); + } + } + else { + type = TYPE_SERIALIZABLE; + + isSerial = Serializable.class.isAssignableFrom(cls); + + writeObjMtds = new ArrayList<>(); + readObjMtds = new ArrayList<>(); + List<List<Field>> fields = new ArrayList<>(); + List<List<T2<OptimizedFieldType, Long>>> fieldOffs = new ArrayList<>(); + List<Map<String, IgniteBiTuple<Integer, OptimizedFieldType>>> fieldInfoMaps = new ArrayList<>(); + List<List<IgniteBiTuple<Integer, OptimizedFieldType>>> fieldInfoLists = 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); + + Field[] clsFields0 = c.getDeclaredFields(); + + Arrays.sort(clsFields0, new Comparator<Field>() { + @Override public int compare(Field f1, Field f2) { + return f1.getName().compareTo(f2.getName()); + } + }); + + List<Field> clsFields = new ArrayList<>(clsFields0.length); + List<T2<OptimizedFieldType, Long>> clsFieldOffs = + new ArrayList<>(clsFields0.length); + + for (int i = 0; i < clsFields0.length; i++) { + Field f = clsFields0[i]; + + int mod = f.getModifiers(); + + if (!isStatic(mod) && !isTransient(mod)) { + OptimizedFieldType type = fieldType(f.getType()); + + clsFields.add(f); + clsFieldOffs.add(new T2<>(type, UNSAFE.objectFieldOffset(f))); + } + } + + fields.add(clsFields); + fieldOffs.add(clsFieldOffs); + + Map<String, IgniteBiTuple<Integer, OptimizedFieldType>> fieldInfoMap = null; + + try { + Field serFieldsDesc = c.getDeclaredField("serialPersistentFields"); + + int mod = serFieldsDesc.getModifiers(); + + if (serFieldsDesc.getType() == ObjectStreamField[].class && + isPrivate(mod) && isStatic(mod) && isFinal(mod)) { + serFieldsDesc.setAccessible(true); + + ObjectStreamField[] serFields = (ObjectStreamField[])serFieldsDesc.get(null); + + fieldInfoMap = new HashMap<>(); + + for (int i = 0; i < serFields.length; i++) { + ObjectStreamField serField = serFields[i]; + + fieldInfoMap.put(serField.getName(), F.t(i, fieldType(serField.getType()))); + } + } + } + catch (NoSuchFieldException ignored) { + // No-op. + } + catch (IllegalAccessException e) { + throw new IOException("Failed to get value of 'serialPersistentFields' field in class: " + + cls.getName(), e); + } + + if (fieldInfoMap == null) { + fieldInfoMap = new HashMap<>(); + + for (int i = 0; i < clsFields.size(); i++) { + Field f = clsFields.get(i); + + fieldInfoMap.put(f.getName(), F.t(i, fieldType(f.getType()))); + } + } + + fieldInfoMaps.add(fieldInfoMap); + + List<IgniteBiTuple<Integer, OptimizedFieldType>> fieldInfoList = + new ArrayList<>(fieldInfoMap.values()); + + Collections.sort(fieldInfoList, new Comparator<IgniteBiTuple<Integer, OptimizedFieldType>>() { + @Override public int compare(IgniteBiTuple<Integer, OptimizedFieldType> t1, + IgniteBiTuple<Integer, OptimizedFieldType> t2) { + return t1.get1().compareTo(t2.get1()); + } + }); + + fieldInfoLists.add(fieldInfoList); + } + + Collections.reverse(writeObjMtds); + Collections.reverse(readObjMtds); + Collections.reverse(fields); + Collections.reverse(fieldOffs); + Collections.reverse(fieldInfoMaps); + Collections.reverse(fieldInfoLists); + + this.fields = new Fields(fields, fieldOffs, fieldInfoLists, fieldInfoMaps); + } + } + } + + shortId = OptimizedMarshallerUtils.computeSerialVersionUid(cls, fields != null ? fields.ownFields() : null).shortValue(); + } + + /** + * @return Excluded flag. + */ + boolean excluded() { + return excluded; + } + + /** + * @return Class. + */ + Class<?> describedClass() { + return cls; + } + + /** + * @return Header. + */ + Integer header() { + return hdr; + } + + /** + * @return ID. + */ + Integer id() { + return id; + } + + /** + * @return Short ID. + */ + Short shortId() { + return shortId; + } + + /** + * @return Class name. + */ + String name() { + return name; + } + + /** + * @return Array component type. + */ + Class<?> componentType() { + return arrCompType; + } + + /** + * @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; + } + + /** + * 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. + */ + @SuppressWarnings("ForLoopReplaceableByForEach") + void write(OptimizedObjectOutputStream out, Object obj) throws IOException { + switch (type) { + case TYPE_BYTE: + out.writeByte((Byte)obj); + + break; + + case TYPE_SHORT: + out.writeShort((Short)obj); + + break; + + case TYPE_INT: + out.writeInt((Integer)obj); + + break; + + case TYPE_LONG: + out.writeLong((Long)obj); + + break; + + case TYPE_FLOAT: + out.writeFloat((Float)obj); + + break; + + case TYPE_DOUBLE: + out.writeDouble((Double)obj); + + break; + + case TYPE_CHAR: + out.writeChar((Character)obj); + + break; + + case TYPE_BOOLEAN: + out.writeBoolean((Boolean)obj); + + break; + + case TYPE_BYTE_ARR: + out.writeByteArray((byte[])obj); + + break; + + case TYPE_SHORT_ARR: + out.writeShortArray((short[])obj); + + break; + + case TYPE_INT_ARR: + out.writeIntArray((int[])obj); + + break; + + case TYPE_LONG_ARR: + out.writeLongArray((long[])obj); + + break; + + case TYPE_FLOAT_ARR: + out.writeFloatArray((float[])obj); + + break; + + case TYPE_DOUBLE_ARR: + out.writeDoubleArray((double[])obj); + + break; + + case TYPE_CHAR_ARR: + out.writeCharArray((char[])obj); + + break; + + case TYPE_BOOLEAN_ARR: + out.writeBooleanArray((boolean[])obj); + + break; + + case TYPE_OBJ_ARR: + out.writeArray((Object[])obj); + + break; + + case TYPE_STR: + out.writeString((String)obj); + + break; + + case TYPE_ENUM: + out.writeInt(((Enum)obj).ordinal()); + + break; + + case TYPE_UUID: + out.writeUuid((UUID)obj); + + break; + + case TYPE_PROPS: + out.writeProperties((Properties)obj, dfltsFieldOff); + + break; + + case TYPE_ARRAY_LIST: + out.writeArrayList((ArrayList<?>)obj); + + break; + + case TYPE_HASH_MAP: + out.writeHashMap((HashMap<?, ?>)obj, loadFactorFieldOff, false); + + break; + + case TYPE_HASH_SET: + out.writeHashSet((HashSet<?>)obj, mapFieldOff, loadFactorFieldOff); + + break; + + case TYPE_LINKED_LIST: + out.writeLinkedList((LinkedList<?>)obj); + + break; + + case TYPE_LINKED_HASH_MAP: + out.writeLinkedHashMap((LinkedHashMap<?, ?>)obj, loadFactorFieldOff, accessOrderFieldOff, false); + + break; + + case TYPE_LINKED_HASH_SET: + out.writeLinkedHashSet((LinkedHashSet<?>)obj, mapFieldOff, loadFactorFieldOff); + + break; + + case TYPE_DATE: + out.writeDate((Date)obj); + + break; + + case TYPE_CLS: + OptimizedClassResolver.writeClass(out, OptimizedMarshallerUtils.classDescriptor((Class<?>) obj, obj)); + + break; + + case TYPE_EXTERNALIZABLE: + out.writeExternalizable(obj); + + break; + + case TYPE_SERIALIZABLE: + if (out.requireSerializable() && !isSerial) + throw new NotSerializableException("Must implement java.io.Serializable or " + + "set GridOptimizedMarshaller.setRequireSerializable() to false " + + "(note that performance may degrade if object is not Serializable): " + name); + + out.writeSerializable(obj, writeObjMtds, fields); + + break; + + default: + throw new IllegalStateException("Invalid class type: " + type); + } + } + + /** + * 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 TYPE_BYTE: + return in.readByte(); + + case TYPE_SHORT: + return in.readShort(); + + case TYPE_INT: + return in.readInt(); + + case TYPE_LONG: + return in.readLong(); + + case TYPE_FLOAT: + return in.readFloat(); + + case TYPE_DOUBLE: + return in.readDouble(); + + case TYPE_CHAR: + return in.readChar(); + + case TYPE_BOOLEAN: + return in.readBoolean(); + + case TYPE_BYTE_ARR: + return in.readByteArray(); + + case TYPE_SHORT_ARR: + return in.readShortArray(); + + case TYPE_INT_ARR: + return in.readIntArray(); + + case TYPE_LONG_ARR: + return in.readLongArray(); + + case TYPE_FLOAT_ARR: + return in.readFloatArray(); + + case TYPE_DOUBLE_ARR: + return in.readDoubleArray(); + + case TYPE_CHAR_ARR: + return in.readCharArray(); + + case TYPE_BOOLEAN_ARR: + return in.readBooleanArray(); + + case TYPE_OBJ_ARR: + return in.readArray(arrCompType); + + case TYPE_STR: + return in.readString(); + + case TYPE_ENUM: + return enumVals[in.readInt()]; + + case TYPE_UUID: + return in.readUuid(); + + case TYPE_PROPS: + return in.readProperties(); + + case TYPE_ARRAY_LIST: + return in.readArrayList(); + + case TYPE_HASH_MAP: + return in.readHashMap(false); + + case TYPE_HASH_SET: + return in.readHashSet(mapFieldOff); + + case TYPE_LINKED_LIST: + return in.readLinkedList(); + + case TYPE_LINKED_HASH_MAP: + return in.readLinkedHashMap(false); + + case TYPE_LINKED_HASH_SET: + return in.readLinkedHashSet(mapFieldOff); + + case TYPE_DATE: + return in.readDate(); + + case TYPE_CLS: + return OptimizedClassResolver.readClass(in, in.classLoader()).describedClass(); + + case TYPE_EXTERNALIZABLE: + return in.readExternalizable(constructor, readResolveMtd); + + case TYPE_SERIALIZABLE: + return in.readSerializable(cls, readObjMtds, readResolveMtd, fields); + + default: + throw new IllegalStateException("Invalid class type: " + type); + } + } + + /** + * @param cls Class. + * @return Type. + */ + @SuppressWarnings("IfMayBeConditional") + 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; + } + + /** + * Encapsulates data about class fields. + */ + @SuppressWarnings("PackageVisibleInnerClass") + static class Fields { + /** Fields. */ + private final List<List<Field>> fields; + + /** Fields offsets. */ + private final List<List<T2<OptimizedFieldType, Long>>> fieldOffs; + + /** Fields details lists. */ + private final List<List<IgniteBiTuple<Integer, OptimizedFieldType>>> fieldInfoLists; + + /** Fields details maps. */ + private final List<Map<String, IgniteBiTuple<Integer, OptimizedFieldType>>> fieldInfoMaps; + + /** + * Creates new instance. + * + * @param fields Fields. + * @param fieldOffs Field offsets. + * @param fieldInfoLists List of field details sequences for each type in the object's class hierarchy. + * @param fieldInfoMaps List of field details maps for each type in the object's class hierarchy. + */ + Fields(List<List<Field>> fields, List<List<T2<OptimizedFieldType, Long>>> fieldOffs, + List<List<IgniteBiTuple<Integer, OptimizedFieldType>>> fieldInfoLists, + List<Map<String, IgniteBiTuple<Integer, OptimizedFieldType>>> fieldInfoMaps) { + this.fields = fields; + this.fieldOffs = fieldOffs; + this.fieldInfoLists = fieldInfoLists; + this.fieldInfoMaps = fieldInfoMaps; + } + + /** + * Returns class's own fields (excluding inherited). + * + * @return List of fields or {@code null} if fields list is empty. + */ + List<Field> ownFields() { + return fields.isEmpty() ? null : fields.get(fields.size() - 1); + } + + /** + * 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. + */ + List<T2<OptimizedFieldType, Long>> fieldOffs(int i) { + return fieldOffs.get(i); + } + + /** + * Returns field sequence numbers and their types as list. + * + * @param i hierarchy level where 0 corresponds to top level. + * @return list of pairs (field number, field type) for the given hierarchy level. + */ + List<IgniteBiTuple<Integer, OptimizedFieldType>> fieldInfoList(int i) { + return fieldInfoLists.get(i); + } + + /** + * Returns field sequence numbers and their types as map where key is a field name, + * + * @param i hierarchy level where 0 corresponds to top level. + * @return map of field names and their details. + */ + Map<String, IgniteBiTuple<Integer, OptimizedFieldType>> fieldInfoMap(int i) { + return fieldInfoMaps.get(i); + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/5674cdde/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedClassResolver.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedClassResolver.java b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedClassResolver.java new file mode 100644 index 0000000..f793857 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedClassResolver.java @@ -0,0 +1,477 @@ +/* + * 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.marshaller.optimized; + +import org.apache.ignite.internal.util.*; +import org.apache.ignite.internal.util.typedef.*; +import org.apache.ignite.internal.util.typedef.internal.*; +import org.apache.ignite.lang.*; +import org.jdk8.backport.*; +import org.jetbrains.annotations.*; + +import java.io.*; +import java.math.*; +import java.sql.*; +import java.util.*; +import java.util.Date; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.concurrent.locks.*; + +/** + * Resolves class names by serialVersionUID. + */ +@SuppressWarnings({"UnnecessaryFullyQualifiedName", "unchecked"}) +class OptimizedClassResolver { + /** File name to generate. */ + private static final String FILE_NAME = "optimized-classnames.properties"; + + /** */ + private static final Map<String, Integer> ggxName2id = new HashMap<>(); + + /** */ + private static final T2<Class<?>, OptimizedClassDescriptor>[] ggxId2name; + + /** */ + private static final Map<String, Integer> ggName2id = new HashMap<>(); + + /** */ + private static final T3<String, Class<?>, OptimizedClassDescriptor>[] ggId2name; + + /** */ + private static Map<String, Integer> usrName2Id; + + /** */ + private static T3<String, Class<?>, OptimizedClassDescriptor>[] usrId2Name; + + /** */ + private static final int HEADER_NAME = 255; + + /** */ + private static final int HEADER_GG_NAME = 254; + + /** */ + private static final int HEADER_USER_NAME = 253; + + /** */ + private static final int HEADER_ARRAY = 252; + + /** + * Initialize predefined classes to optimize. + */ + static { + Class[] superOptCls = new Class[] { + // Array types. + byte[].class, + short[].class, + int[].class, + long[].class, + float[].class, + double[].class, + boolean[].class, + char[].class, + + // Boxed types. + Byte.class, + Short.class, + Integer.class, + Long.class, + Float.class, + Double.class, + Boolean.class, + Character.class, + String.class, + + // Atomic. + AtomicBoolean.class,AtomicInteger.class, + AtomicLong.class,AtomicReference.class, + AtomicMarkableReference.class, + AtomicStampedReference.class, + AtomicIntegerArray.class, + AtomicReferenceArray.class, + + // Concurrent types. + ConcurrentHashMap.class, + ConcurrentLinkedQueue.class, + ConcurrentSkipListMap.class, + ConcurrentSkipListSet.class, + LinkedBlockingDeque.class, + LinkedBlockingQueue.class, + PriorityBlockingQueue.class, + CopyOnWriteArrayList.class, + CopyOnWriteArraySet.class, + + // Locks. + ReentrantLock.class, + ReentrantReadWriteLock.class, + ReentrantReadWriteLock.ReadLock.class, + ReentrantReadWriteLock.WriteLock.class, + + // Util types. + Date.class, + UUID.class, + Calendar.class, + Random.class, + Calendar.class, + Currency.class, + ArrayList.class, + LinkedList.class, + Stack.class, + Vector.class, + HashMap.class, + HashSet.class, + Hashtable.class, + TreeMap.class, + TreeSet.class, + IdentityHashMap.class, + LinkedHashMap.class, + LinkedHashSet.class, + ArrayDeque.class, + BitSet.class, + EnumMap.class, + EnumSet.class, + + // SQL types. + java.sql.Date.class, + Time.class, + Timestamp.class, + + // Math types. + BigDecimal.class, + BigInteger.class, + + // GridGain types. + IgniteUuid.class, + GridBoundedConcurrentOrderedSet.class, + GridBoundedLinkedHashSet.class, + GridConcurrentHashSet.class, + ConcurrentLinkedDeque8.class, + GridConcurrentPhantomHashSet.class, + GridConcurrentSkipListSet.class, + GridConcurrentWeakHashSet.class, + GridIdentityHashSet.class, + GridLeanSet.class, + GridSetWrapper.class + }; + + // Have to leave a range for special purposes. + assert superOptCls.length < 230; + + ggxId2name = new T2[superOptCls.length]; + + for (int i = 0; i < superOptCls.length; i++) { + Class cls = superOptCls[i]; + + ggxName2id.put(cls.getName(), i); + ggxId2name[i] = new T2<Class<?>, OptimizedClassDescriptor>(cls, null); + } + + BufferedReader reader = new BufferedReader(new InputStreamReader( + OptimizedClassResolver.class.getResourceAsStream(FILE_NAME), + OptimizedMarshallerUtils.UTF_8)); + + List<T3<String, Class<?>, OptimizedClassDescriptor>> ggId2name0 = + new LinkedList<>(); + + try { + for (int i = 0; ; i++) { + String clsName = reader.readLine(); + + if (clsName == null) + break; + + ggName2id.put(clsName, i); + ggId2name0.add(new T3<String, Class<?>, OptimizedClassDescriptor>(clsName, null, null)); + } + + ggId2name = ggId2name0.toArray(new T3[ggId2name0.size()]); + } + catch (IOException e) { + throw new AssertionError(e); + } + finally { + U.close(reader, null); + } + } + + /** + * Ensure singleton. + */ + private OptimizedClassResolver() { + // No-op. + } + + /** + * @param usrName2id0 From name to ID. + * @param usrId2Name0 From ID to name. + */ + static void userClasses(@Nullable Map<String, Integer> usrName2id0, + @Nullable T3<String, Class<?>, OptimizedClassDescriptor>[] usrId2Name0) { + usrName2Id = usrName2id0; + usrId2Name = usrId2Name0; + } + + /** + * @param in DataInput to read from. + * @param clsLdr ClassLoader. + * @return Class descriptor. + * @throws IOException If serial version UID failed. + * @throws ClassNotFoundException If the class cannot be located by the specified class loader. + */ + static OptimizedClassDescriptor readClass(DataInput in, ClassLoader clsLdr) + throws IOException, ClassNotFoundException { + assert in != null; + assert clsLdr != null; + + int hdr = in.readByte() & 0xff; + + if (hdr < ggxId2name.length) { + T2<Class<?>, OptimizedClassDescriptor> ggxT = ggxId2name[hdr]; + + OptimizedClassDescriptor desc = ggxT.get2(); + + if (desc == null) { + desc = OptimizedMarshallerUtils.classDescriptor(ggxT.get1(), null); + + ggxT.set2(desc); + } + + return desc; + } + + String name; + Class<?> cls; + OptimizedClassDescriptor desc; + + switch (hdr) { + case HEADER_GG_NAME: + int ggId = in.readInt(); + + T3<String, Class<?>, OptimizedClassDescriptor> ggT; + + try { + ggT = ggId2name[ggId]; + } + catch (ArrayIndexOutOfBoundsException e) { + throw new ClassNotFoundException("Failed to find optimized class ID " + + "(is same Ignite version running on all nodes?): " + ggId, e); + } + + name = ggT.get1(); + cls = ggT.get2(); + desc = ggT.get3(); + + if (desc == null) { + if (clsLdr == U.gridClassLoader()) { + if (cls == null) { + cls = forName(name, clsLdr); + + ggT.set2(cls); + } + + desc = OptimizedMarshallerUtils.classDescriptor(cls, null); + + ggT.set3(desc); + } + else { + cls = forName(name, clsLdr); + + desc = OptimizedMarshallerUtils.classDescriptor(cls, null); + } + } + + break; + + case HEADER_USER_NAME: + int usrId = in.readInt(); + + T3<String, Class<?>, OptimizedClassDescriptor> usrT; + + try { + if (usrId2Name != null) + usrT = usrId2Name[usrId]; + else + throw new ClassNotFoundException("Failed to find user defined class ID " + + "(make sure to register identical classes on all nodes for optimization): " + usrId); + } + catch (ArrayIndexOutOfBoundsException e) { + throw new ClassNotFoundException("Failed to find user defined class ID " + + "(make sure to register identical classes on all nodes for optimization): " + usrId, e); + } + + name = usrT.get1(); + cls = usrT.get2(); + desc = usrT.get3(); + + if (desc == null) { + if (cls == null) { + cls = forName(name, clsLdr); + + usrT.set2(cls); + } + + desc = OptimizedMarshallerUtils.classDescriptor(cls, null); + + usrT.set3(desc); + } + + break; + + case HEADER_ARRAY: + name = readClass(in, clsLdr).name(); + + name = name.charAt(0) == '[' ? "[" + name : "[L" + name + ';'; + + cls = forName(name, clsLdr); + + return OptimizedMarshallerUtils.classDescriptor(cls, null); + + case HEADER_NAME: + name = in.readUTF(); + + cls = forName(name, clsLdr); + + desc = OptimizedMarshallerUtils.classDescriptor(cls, null); + + break; + + default: + throw new IOException("Unexpected optimized stream header: " + hdr); + } + + short actual = desc.shortId(); + + short exp = in.readShort(); + + if (actual != exp) + throw new ClassNotFoundException("Optimized stream class checksum mismatch " + + "(is same version of marshalled class present on all nodes?) " + + "[expected=" + exp + ", actual=" + actual + ", cls=" + cls + ']'); + + return desc; + } + + /** + * @param out Output. + * @param desc Class descriptor. + * @throws IOException In case of error. + */ + static void writeClass(DataOutput out, OptimizedClassDescriptor desc) throws IOException { + assert out != null; + assert desc != null; + + int hdr = desc.header(); + + out.writeByte(hdr); + + switch (hdr) { + case HEADER_GG_NAME: + case HEADER_USER_NAME: + out.writeInt(desc.id()); + out.writeShort(desc.shortId()); + + return; + + case HEADER_ARRAY: + writeClass(out, OptimizedMarshallerUtils.classDescriptor(desc.componentType(), null)); + + return; + + case HEADER_NAME: + out.writeUTF(desc.name()); + out.writeShort(desc.shortId()); + } + } + + /** + * @param cls Class to write. + * @return Data for {@code writeClass} method. + */ + static T2<Integer, Integer> writeClassData(Class<?> cls) { + assert cls != null; + + String name = cls.getName(); + + Integer superHdr = ggxName2id.get(name); + + if (superHdr != null) + return new T2<>(superHdr, null); + + Integer id; + + if ((id = ggName2id.get(name)) != null) + return new T2<>(HEADER_GG_NAME, id); + + if (usrName2Id != null && (id = usrName2Id.get(name)) != null) + return new T2<>(HEADER_USER_NAME, id); + + if (cls.isArray()) + return new T2<>(HEADER_ARRAY, null); + + return new T2<>(HEADER_NAME, null); + } + + /** + * @param name Class name. + * @param ldr Class loader. + * @return Class. + * @throws ClassNotFoundException If class not found. + */ + private static Class<?> forName(String name, ClassLoader ldr) throws ClassNotFoundException { + Class<?> cls = primitive(name); + + if (cls == null) + cls = OptimizedMarshallerUtils.forName(name, ldr); + + return cls; + } + + /** + * @param name Name of primitive class. + * @return Primitive type class or null. + */ + @SuppressWarnings("TypeMayBeWeakened") + @Nullable private static Class<?> primitive(String name) { + if (name.length() > 7) + return null; + + switch (name.charAt(0)) { + case 'b': + if ("boolean".equals(name)) + return boolean.class; + + return "byte".equals(name) ? byte.class : null; + case 's': + return "short".equals(name) ? short.class : null; + case 'i': + return "int".equals(name) ? int.class : null; + case 'l': + return "long".equals(name) ? long.class : null; + case 'c': + return "char".equals(name) ? char.class : null; + case 'f': + return "float".equals(name) ? float.class : null; + case 'd': + return "double".equals(name) ? double.class : null; + case 'v': + return "void".equals(name) ? void.class : null; + } + + return null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/5674cdde/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedFieldType.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedFieldType.java b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedFieldType.java new file mode 100644 index 0000000..3840dfd --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedFieldType.java @@ -0,0 +1,50 @@ +/* + * 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.marshaller.optimized; + +/** + * Field type used to calculate {@code Unsafe} offsets into objects. + */ +enum OptimizedFieldType { + /** */ + BYTE, + + /** */ + SHORT, + + /** */ + INT, + + /** */ + LONG, + + /** */ + FLOAT, + + /** */ + DOUBLE, + + /** */ + CHAR, + + /** */ + BOOLEAN, + + /** */ + OTHER +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/5674cdde/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshallable.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshallable.java b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshallable.java new file mode 100644 index 0000000..a4a8d36 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshallable.java @@ -0,0 +1,65 @@ +/* + * 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.marshaller.optimized; + +import java.util.*; + +/** + * Optional interface which helps make serialization even faster by removing internal + * look-ups for classes. + * <p> + * All implementation must have the following: + * <ul> + * <li> + * Must have static filed (private or public) declared of type {@link Object} + * with name {@code GG_CLASS_ID}. GridGain will reflectively initialize this field with + * proper class ID during system startup. + * </li> + * <li> + * Must return the value of {@code GG_CLASS_ID} field from {@link #ggClassId} method. + * </li> + * </ul> + * Here is a sample implementation: + * <pre name="code" class="java"> + * // For better performance consider implementing java.io.Externalizable interface. + * class ExampleMarshallable implements GridOptimizedMarshallable, Serializable { + * // Class ID field required by 'GridOptimizedMarshallable'. + * private static Object GG_CLASS_ID; + * + * ... + * + * @ public Object ggClassId() { + * return GG_CLASS_ID; + * } + * } + * </pre> + * <p> + * Note that for better performance you should also specify list of classes you + * plan to serialize via {@link OptimizedMarshaller#setClassNames(List)} method. + */ +public interface OptimizedMarshallable { + /** */ + public static final String CLS_ID_FIELD_NAME = "GG_CLASS_ID"; + + /** + * Implementation of this method should simply return value of {@code GG_CLASS_ID} field. + * + * @return Class ID for optimized marshalling. + */ + public Object ggClassId(); +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/5674cdde/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshaller.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshaller.java b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshaller.java new file mode 100644 index 0000000..7f8a3f0 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshaller.java @@ -0,0 +1,393 @@ +/* + * 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.marshaller.optimized; + +import org.apache.ignite.*; +import org.apache.ignite.internal.util.*; +import org.apache.ignite.internal.util.typedef.*; +import org.apache.ignite.internal.util.typedef.internal.*; +import org.apache.ignite.marshaller.*; +import org.jetbrains.annotations.*; +import sun.misc.*; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * Optimized implementation of {@link org.apache.ignite.marshaller.Marshaller}. Unlike {@link org.apache.ignite.marshaller.jdk.JdkMarshaller}, + * which is based on standard {@link ObjectOutputStream}, this marshaller does not + * enforce that all serialized objects implement {@link Serializable} interface. It is also + * about 20 times faster as it removes lots of serialization overhead that exists in + * default JDK implementation. + * <p> + * {@code GridOptimizedMarshaller} is tested only on Java HotSpot VM on other VMs + * it could yield unexpected results. It is the default marshaller on Java HotSpot VMs + * and will be used if no other marshaller was explicitly configured. + * <p> + * <h1 class="header">Configuration</h1> + * <h2 class="header">Mandatory</h2> + * This marshaller has no mandatory configuration parameters. + * <h2 class="header">Java Example</h2> + * <pre name="code" class="java"> + * GridOptimizedMarshaller marshaller = new GridOptimizedMarshaller(); + * + * // Enforce Serializable interface. + * marshaller.setRequireSerializable(true); + * + * GridConfiguration cfg = new GridConfiguration(); + * + * // Override marshaller. + * cfg.setMarshaller(marshaller); + * + * // Starts grid. + * G.start(cfg); + * </pre> + * <h2 class="header">Spring Example</h2> + * GridOptimizedMarshaller can be configured from Spring XML configuration file: + * <pre name="code" class="xml"> + * <bean id="grid.custom.cfg" class="org.gridgain.grid.GridConfiguration" singleton="true"> + * ... + * <property name="marshaller"> + * <bean class="org.apache.ignite.marshaller.optimized.GridOptimizedMarshaller"> + * <property name="requireSerializable">true</property> + * </bean> + * </property> + * ... + * </bean> + * </pre> + * <p> + * <img src="http://www.gridgain.com/images/spring-small.png"> + * <br> + * For information about Spring framework visit <a href="http://www.springframework.org/">www.springframework.org</a> + */ +public class OptimizedMarshaller extends AbstractMarshaller { + /** Whether or not to require an object to be serializable in order to be marshalled. */ + private boolean requireSer = true; + + /** Default class loader. */ + private final ClassLoader dfltClsLdr = getClass().getClassLoader(); + + /** + * Initializes marshaller not to enforce {@link Serializable} interface. + * + * @throws IgniteException If this marshaller is not supported on the current JVM. + */ + public OptimizedMarshaller() { + if (!available()) + throw new IgniteException("Using GridOptimizedMarshaller on unsupported JVM version (some of " + + "JVM-private APIs required for the marshaller to work are missing)."); + } + + /** + * Initializes marshaller with given serialization flag. If {@code true}, + * then objects will be required to implement {@link Serializable} in order + * to be serialize. + * + * @param requireSer Flag to enforce {@link Serializable} interface or not. If {@code true}, + * then objects will be required to implement {@link Serializable} in order to be + * marshalled, if {@code false}, then such requirement will be relaxed. + * @throws IgniteException If this marshaller is not supported on the current JVM. + */ + public OptimizedMarshaller(boolean requireSer) { + this(); + + this.requireSer = requireSer; + } + + /** + * Initializes marshaller with given serialization flag. If {@code true}, + * then objects will be required to implement {@link Serializable} in order + * to be serialize. + * + * @param requireSer Flag to enforce {@link Serializable} interface or not. If {@code true}, + * then objects will be required to implement {@link Serializable} in order to be + * marshalled, if {@code false}, then such requirement will be relaxed. + * @param clsNames User preregistered class names. + * @param clsNamesPath Path to a file with user preregistered class names. + * @param poolSize Object streams pool size. + * @throws IgniteCheckedException If an I/O error occurs while writing stream header. + * @throws IgniteException If this marshaller is not supported on the current JVM. + */ + public OptimizedMarshaller(boolean requireSer, @Nullable List<String> clsNames, + @Nullable String clsNamesPath, int poolSize) throws IgniteCheckedException { + this(requireSer); + + setClassNames(clsNames); + setClassNamesPath(clsNamesPath); + setPoolSize(poolSize); + } + + /** + * Adds provided class names for marshalling optimization. + * <p> + * <b>NOTE</b>: these collections of classes must be identical on all nodes and in the same order. + * + * @param clsNames User preregistered class names to add. + */ + @SuppressWarnings("unchecked") + public void setClassNames(@Nullable List<String> clsNames) { + if (clsNames != null && !clsNames.isEmpty()) { + String[] clsNamesArr = clsNames.toArray(new String[clsNames.size()]); + + Arrays.sort(clsNamesArr); + + Map<String, Integer> name2id = U.newHashMap(clsNamesArr.length); + T3<String, Class<?>, OptimizedClassDescriptor>[] id2name = new T3[clsNamesArr.length]; + + int i = 0; + + for (String name : clsNamesArr) { + name2id.put(name, i); + id2name[i++] = new T3<>(name, null, null); + } + + OptimizedClassResolver.userClasses(name2id, id2name); + } + } + + /** + * Specifies a name of the file which lists all class names to be optimized. + * The file path can either be absolute path, relative to {@code IGNITE_HOME}, + * or specify a resource file on the class path. + * <p> + * The format of the file is class name per line, like this: + * <pre> + * ... + * com.example.Class1 + * com.example.Class2 + * ... + * </pre> + * <p> + * <b>NOTE</b>: this class list must be identical on all nodes and in the same order. + * + * @param path Path to a file with user preregistered class names. + * @throws IgniteCheckedException If an error occurs while writing stream header. + */ + public void setClassNamesPath(@Nullable String path) throws IgniteCheckedException { + if (path == null) + return; + + URL url = IgniteUtils.resolveGridGainUrl(path, false); + + if (url == null) + throw new IgniteCheckedException("Failed to find resource for name: " + path); + + List<String> clsNames; + + try { + clsNames = new LinkedList<>(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), OptimizedMarshallerUtils.UTF_8))) { + String clsName; + + while ((clsName = reader.readLine()) != null) + clsNames.add(clsName); + } + } + catch (IOException e) { + throw new IgniteCheckedException("Failed to read class names from path: " + path, e); + } + + setClassNames(clsNames); + } + + /** + * Specifies size of cached object streams used by marshaller. Object streams are cached for + * performance reason to avoid costly recreation for every serialization routine. If {@code 0} (default), + * pool is not used and each thread has its own cached object stream which it keeps reusing. + * <p> + * Since each stream has an internal buffer, creating a stream for each thread can lead to + * high memory consumption if many large messages are marshalled or unmarshalled concurrently. + * Consider using pool in this case. This will limit number of streams that can be created and, + * therefore, decrease memory consumption. + * <p> + * NOTE: Using streams pool can decrease performance since streams will be shared between + * different threads which will lead to more frequent context switching. + * + * @param poolSize Streams pool size. If {@code 0}, pool is not used. + */ + public void setPoolSize(int poolSize) { + OptimizedObjectStreamRegistry.poolSize(poolSize); + } + + /** + * @return Whether to enforce {@link Serializable} interface. + */ + public boolean isRequireSerializable() { + return requireSer; + } + + /** + * Sets flag to enforce {@link Serializable} interface or not. + * + * @param requireSer Flag to enforce {@link Serializable} interface or not. If {@code true}, + * then objects will be required to implement {@link Serializable} in order to be + * marshalled, if {@code false}, then such requirement will be relaxed. + */ + public void setRequireSerializable(boolean requireSer) { + this.requireSer = requireSer; + } + + /** {@inheritDoc} */ + @Override public void marshal(@Nullable Object obj, OutputStream out) throws IgniteCheckedException { + assert out != null; + + OptimizedObjectOutputStream objOut = null; + + try { + objOut = OptimizedObjectStreamRegistry.out(); + + objOut.requireSerializable(requireSer); + + objOut.out().outputStream(out); + + objOut.writeObject(obj); + } + catch (IOException e) { + throw new IgniteCheckedException("Failed to serialize object: " + obj, e); + } + finally { + OptimizedObjectStreamRegistry.closeOut(objOut); + } + } + + /** {@inheritDoc} */ + @Override public byte[] marshal(@Nullable Object obj) throws IgniteCheckedException { + OptimizedObjectOutputStream objOut = null; + + try { + objOut = OptimizedObjectStreamRegistry.out(); + + objOut.requireSerializable(requireSer); + + objOut.writeObject(obj); + + return objOut.out().array(); + } + catch (IOException e) { + throw new IgniteCheckedException("Failed to serialize object: " + obj, e); + } + finally { + OptimizedObjectStreamRegistry.closeOut(objOut); + } + } + + /** {@inheritDoc} */ + @Override public <T> T unmarshal(InputStream in, @Nullable ClassLoader clsLdr) throws IgniteCheckedException { + assert in != null; + + OptimizedObjectInputStream objIn = null; + + try { + objIn = OptimizedObjectStreamRegistry.in(); + + objIn.classLoader(clsLdr != null ? clsLdr : dfltClsLdr); + + objIn.in().inputStream(in); + + return (T)objIn.readObject(); + } + catch (IOException e) { + throw new IgniteCheckedException("Failed to deserialize object with given class loader: " + clsLdr, e); + } + catch (ClassNotFoundException e) { + throw new IgniteCheckedException("Failed to find class with given class loader for unmarshalling " + + "(make sure same versions of all classes are available on all nodes or enable peer-class-loading): " + + clsLdr, e); + } + finally { + OptimizedObjectStreamRegistry.closeIn(objIn); + } + } + + /** {@inheritDoc} */ + @Override public <T> T unmarshal(byte[] arr, @Nullable ClassLoader clsLdr) throws IgniteCheckedException { + assert arr != null; + + OptimizedObjectInputStream objIn = null; + + try { + objIn = OptimizedObjectStreamRegistry.in(); + + objIn.classLoader(clsLdr != null ? clsLdr : dfltClsLdr); + + objIn.in().bytes(arr, arr.length); + + return (T)objIn.readObject(); + } + catch (IOException e) { + throw new IgniteCheckedException("Failed to deserialize object with given class loader: " + clsLdr, e); + } + catch (ClassNotFoundException e) { + throw new IgniteCheckedException("Failed to find class with given class loader for unmarshalling " + + "(make sure same version of all classes are available on all nodes or enable peer-class-loading): " + + clsLdr, e); + } + finally { + OptimizedObjectStreamRegistry.closeIn(objIn); + } + } + + /** + * Checks whether {@code GridOptimizedMarshaller} is able to work on the current JVM. + * <p> + * As long as {@code GridOptimizedMarshaller} uses JVM-private API, which is not guaranteed + * to be available on all JVM, this method should be called to ensure marshaller could work properly. + * <p> + * Result of this method is automatically checked in constructor. + * + * @return {@code true} if {@code GridOptimizedMarshaller} can work on the current JVM or + * {@code false} if it can't. + */ + @SuppressWarnings({"TypeParameterExtendsFinalClass", "ErrorNotRethrown"}) + public static boolean available() { + try { + Unsafe unsafe = GridUnsafe.unsafe(); + + Class<? extends Unsafe> unsafeCls = unsafe.getClass(); + + unsafeCls.getMethod("allocateInstance", Class.class); + unsafeCls.getMethod("copyMemory", Object.class, long.class, Object.class, long.class, long.class); + + return true; + } + catch (Exception ignored) { + return false; + } + catch (NoClassDefFoundError ignored) { + return false; + } + } + + /** + * Undeployment callback invoked when class loader is being undeployed. + * + * @param ldr Class loader being undeployed. + */ + public static void onUndeploy(ClassLoader ldr) { + OptimizedMarshallerUtils.onUndeploy(ldr); + } + + /** + * Clears internal caches and frees memory. Usually called on system stop. + */ + public static void clearCache() { + OptimizedMarshallerUtils.clearCache(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/5674cdde/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshallerUtils.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshallerUtils.java b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshallerUtils.java new file mode 100644 index 0000000..6d8ebfc --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/marshaller/optimized/OptimizedMarshallerUtils.java @@ -0,0 +1,458 @@ +/* + * 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.marshaller.optimized; + +import org.apache.ignite.internal.util.*; +import org.apache.ignite.internal.util.typedef.*; +import org.jdk8.backport.*; +import org.jetbrains.annotations.*; +import sun.misc.*; + +import java.io.*; +import java.lang.reflect.*; +import java.nio.charset.*; +import java.security.*; +import java.util.*; +import java.util.concurrent.*; + +import static org.apache.ignite.marshaller.optimized.OptimizedMarshallable.*; + +/** + * Miscellaneous utility methods to facilitate {@link OptimizedMarshaller}. + */ +class OptimizedMarshallerUtils { + /** Unsafe. */ + private static final Unsafe UNSAFE = GridUnsafe.unsafe(); + + /** {@code Null} object reference. */ + static final byte NULL = (byte)0x70; + + /** Handle reference. */ + static final byte HANDLE = (byte)0x71; + + /** Object reference. */ + static final byte OBJECT = (byte)0x72; + + /** UTF-8 character name. */ + static final Charset UTF_8 = Charset.forName("UTF-8"); + + /** Class descriptors cache. */ + private static final ConcurrentMap<Class<?>, OptimizedClassDescriptor> CLS_DESC_CACHE = + new ConcurrentHashMap8<>(256); + + /** Classes cache by name. */ + private static final ConcurrentHashMap8<ClassLoader, ConcurrentHashMap8<String, Class<?>>> CLS_BY_NAME_CACHE = + new ConcurrentHashMap8<>(); + + /** + * Suppresses default constructor, ensuring non-instantiability. + */ + private OptimizedMarshallerUtils() { + // No-op. + } + + /** + * Gets class for given name and class loader. + * + * @param name Class name. + * @param ldr Class loader. + * @return Class. + * @throws ClassNotFoundException If class was not found. + */ + static Class<?> forName(String name, ClassLoader ldr) throws ClassNotFoundException { + assert ldr != null; + assert name != null; + + ConcurrentHashMap8<String, Class<?>> cache = CLS_BY_NAME_CACHE.get(ldr); + + Class<?> cls = null; + + if (cache == null) { + cache = new ConcurrentHashMap8<>(); + + ConcurrentHashMap8<String, Class<?>> old = CLS_BY_NAME_CACHE.putIfAbsent(ldr, cache); + + if (old != null) { + cache = old; + + cls = cache.get(name); + } + } + else + cls = cache.get(name); + + if (cls == null) { + cls = Class.forName(name, true, ldr); + + cache.put(name, cls); + } + + return cls; + } + + /** + * Gets descriptor for provided class. + * + * @param cls Class. + * @param obj Object. + * @return Descriptor. + * @throws IOException In case of error. + */ + static OptimizedClassDescriptor classDescriptor(Class<?> cls, @Nullable Object obj) throws IOException { + if (obj != null) { + if (obj instanceof OptimizedMarshallable) { + OptimizedMarshallable m = (OptimizedMarshallable)obj; + + Object clsId = m.ggClassId(); + + if (clsId != null && !(clsId instanceof OptimizedClassDescriptor)) + throw new IOException("Method '" + obj.getClass().getName() + ".ggClassId() must return " + + "the value of the field '" + CLS_ID_FIELD_NAME + "'."); + + OptimizedClassDescriptor desc = (OptimizedClassDescriptor)clsId; + + if (desc == null) { + desc = new OptimizedClassDescriptor(cls); + + try { + Field field = obj.getClass().getDeclaredField(CLS_ID_FIELD_NAME); + + field.setAccessible(true); + + Object o = field.get(null); + + if (o == null) { + if ((field.getModifiers() & Modifier.STATIC) == 0) + throw new IOException("Field '" + CLS_ID_FIELD_NAME + "' must be declared static: " + + obj.getClass().getName()); + + field.set(null, desc); + + if (m.ggClassId() == null) + throw new IOException( "Method '" + obj.getClass().getName() + ".ggClassId() must " + + "return the value of the field '" + CLS_ID_FIELD_NAME + "': " + + obj.getClass().getName()); + } + else if (!(o instanceof OptimizedClassDescriptor)) + throw new IOException("Field '" + CLS_ID_FIELD_NAME + "' must be declared with " + + "null value: " + obj.getClass().getName()); + } + catch (NoSuchFieldException e) { + throw new IOException("GridOptimizedMarshallable classes must have static field declared " + + "[fieldName=" + CLS_ID_FIELD_NAME + ", cls=" + obj.getClass().getName() + ']', e); + } + catch (IllegalAccessException e) { + throw new IOException("Failed to set field '" + CLS_ID_FIELD_NAME + "' on '" + + obj.getClass().getName() + "' class.", e); + } + } + + return desc; + } + } + + OptimizedClassDescriptor desc = CLS_DESC_CACHE.get(cls); + + if (desc == null) { + OptimizedClassDescriptor existing = CLS_DESC_CACHE.putIfAbsent(cls, + desc = new OptimizedClassDescriptor(cls)); + + if (existing != null) + desc = existing; + } + + return desc; + } + + /** + * Undeployment callback. + * + * @param ldr Undeployed class loader. + */ + public static void onUndeploy(ClassLoader ldr) { + CLS_BY_NAME_CACHE.remove(ldr); + + for (Class<?> cls : CLS_DESC_CACHE.keySet()) { + if (ldr.equals(cls.getClassLoader())) + CLS_DESC_CACHE.remove(cls); + } + } + + /** + * Intended for test purposes only. + */ + public static void clearCache() { + CLS_BY_NAME_CACHE.clear(); + CLS_DESC_CACHE.clear(); + } + + /** + * + */ + public static void printMemoryStats() { + X.println(">>>"); + X.println(">>> GridOptimizedMarshallerUtils memory stats:"); + X.println(" Cache size: " + CLS_DESC_CACHE.size()); + + for (Map.Entry<Class<?>, OptimizedClassDescriptor> e : CLS_DESC_CACHE.entrySet()) + X.println(" " + e.getKey() + " : " + e.getValue()); + } + + /** + * Computes the serial version UID value for the given class. + * The code is taken from {@link ObjectStreamClass#computeDefaultSUID(Class)}. + * + * @param cls A class. + * @param fields Fields. + * @return A serial version UID. + * @throws IOException If failed. + */ + @SuppressWarnings("ForLoopReplaceableByForEach") + static Long computeSerialVersionUid(Class cls, List<Field> fields) throws IOException { + if (Serializable.class.isAssignableFrom(cls) && !Enum.class.isAssignableFrom(cls)) + return ObjectStreamClass.lookup(cls).getSerialVersionUID(); + + MessageDigest md; + + try { + md = MessageDigest.getInstance("SHA"); + } + catch (NoSuchAlgorithmException e) { + throw new IOException("Failed to get digest for SHA.", e); + } + + md.update(cls.getName().getBytes(UTF_8)); + + if (!F.isEmpty(fields)) { + for (int i = 0; i < fields.size(); i++) { + Field f = fields.get(i); + + md.update(f.getName().getBytes(UTF_8)); + md.update(f.getType().getName().getBytes(UTF_8)); + } + } + + byte[] hashBytes = md.digest(); + + long hash = 0; + + // Composes a single-long hash from the byte[] hash. + for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) + hash = (hash << 8) | (hashBytes[i] & 0xFF); + + return hash; + } + + /** + * Gets byte field value. + * + * @param obj Object. + * @param off Field offset. + * @return Byte value. + */ + static byte getByte(Object obj, long off) { + return UNSAFE.getByte(obj, off); + } + + /** + * Sets byte field value. + * + * @param obj Object. + * @param off Field offset. + * @param val Value. + */ + static void setByte(Object obj, long off, byte val) { + UNSAFE.putByte(obj, off, val); + } + + /** + * Gets short field value. + * + * @param obj Object. + * @param off Field offset. + * @return Short value. + */ + static short getShort(Object obj, long off) { + return UNSAFE.getShort(obj, off); + } + + /** + * Sets short field value. + * + * @param obj Object. + * @param off Field offset. + * @param val Value. + */ + static void setShort(Object obj, long off, short val) { + UNSAFE.putShort(obj, off, val); + } + + /** + * Gets integer field value. + * + * @param obj Object. + * @param off Field offset. + * @return Integer value. + */ + static int getInt(Object obj, long off) { + return UNSAFE.getInt(obj, off); + } + + /** + * Sets integer field value. + * + * @param obj Object. + * @param off Field offset. + * @param val Value. + */ + static void setInt(Object obj, long off, int val) { + UNSAFE.putInt(obj, off, val); + } + + /** + * Gets long field value. + * + * @param obj Object. + * @param off Field offset. + * @return Long value. + */ + static long getLong(Object obj, long off) { + return UNSAFE.getLong(obj, off); + } + + /** + * Sets long field value. + * + * @param obj Object. + * @param off Field offset. + * @param val Value. + */ + static void setLong(Object obj, long off, long val) { + UNSAFE.putLong(obj, off, val); + } + + /** + * Gets float field value. + * + * @param obj Object. + * @param off Field offset. + * @return Float value. + */ + static float getFloat(Object obj, long off) { + return UNSAFE.getFloat(obj, off); + } + + /** + * Sets float field value. + * + * @param obj Object. + * @param off Field offset. + * @param val Value. + */ + static void setFloat(Object obj, long off, float val) { + UNSAFE.putFloat(obj, off, val); + } + + /** + * Gets double field value. + * + * @param obj Object. + * @param off Field offset. + * @return Double value. + */ + static double getDouble(Object obj, long off) { + return UNSAFE.getDouble(obj, off); + } + + /** + * Sets double field value. + * + * @param obj Object. + * @param off Field offset. + * @param val Value. + */ + static void setDouble(Object obj, long off, double val) { + UNSAFE.putDouble(obj, off, val); + } + + /** + * Gets char field value. + * + * @param obj Object. + * @param off Field offset. + * @return Char value. + */ + static char getChar(Object obj, long off) { + return UNSAFE.getChar(obj, off); + } + + /** + * Sets char field value. + * + * @param obj Object. + * @param off Field offset. + * @param val Value. + */ + static void setChar(Object obj, long off, char val) { + UNSAFE.putChar(obj, off, val); + } + + /** + * Gets boolean field value. + * + * @param obj Object. + * @param off Field offset. + * @return Boolean value. + */ + static boolean getBoolean(Object obj, long off) { + return UNSAFE.getBoolean(obj, off); + } + + /** + * Sets boolean field value. + * + * @param obj Object. + * @param off Field offset. + * @param val Value. + */ + static void setBoolean(Object obj, long off, boolean val) { + UNSAFE.putBoolean(obj, off, val); + } + + /** + * Gets field value. + * + * @param obj Object. + * @param off Field offset. + * @return Value. + */ + static Object getObject(Object obj, long off) { + return UNSAFE.getObject(obj, off); + } + + /** + * Sets field value. + * + * @param obj Object. + * @param off Field offset. + * @param val Value. + */ + static void setObject(Object obj, long off, Object val) { + UNSAFE.putObject(obj, off, val); + } +}
