| /* |
| * 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.IOException; |
| import java.io.ObjectStreamClass; |
| import java.io.Serializable; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| import java.nio.charset.Charset; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.concurrent.ConcurrentMap; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.IgniteException; |
| import org.apache.ignite.internal.util.GridUnsafe; |
| import org.apache.ignite.internal.util.typedef.F; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.marshaller.MarshallerContext; |
| import org.jetbrains.annotations.NotNull; |
| |
| import static org.apache.ignite.internal.MarshallerPlatformIds.JAVA_ID; |
| |
| /** |
| * Miscellaneous utility methods to facilitate {@link OptimizedMarshaller}. |
| */ |
| class OptimizedMarshallerUtils { |
| /** */ |
| static final long HASH_SET_MAP_OFF; |
| |
| /** */ |
| static final byte JDK = -2; |
| |
| /** */ |
| static final byte HANDLE = -1; |
| |
| /** */ |
| static final byte NULL = 0; |
| |
| /** */ |
| static final byte BYTE = 1; |
| |
| /** */ |
| static final byte SHORT = 2; |
| |
| /** */ |
| static final byte INT = 3; |
| |
| /** */ |
| static final byte LONG = 4; |
| |
| /** */ |
| static final byte FLOAT = 5; |
| |
| /** */ |
| static final byte DOUBLE = 6; |
| |
| /** */ |
| static final byte CHAR = 7; |
| |
| /** */ |
| static final byte BOOLEAN = 8; |
| |
| /** */ |
| static final byte BYTE_ARR = 9; |
| |
| /** */ |
| static final byte SHORT_ARR = 10; |
| |
| /** */ |
| static final byte INT_ARR = 11; |
| |
| /** */ |
| static final byte LONG_ARR = 12; |
| |
| /** */ |
| static final byte FLOAT_ARR = 13; |
| |
| /** */ |
| static final byte DOUBLE_ARR = 14; |
| |
| /** */ |
| static final byte CHAR_ARR = 15; |
| |
| /** */ |
| static final byte BOOLEAN_ARR = 16; |
| |
| /** */ |
| static final byte OBJ_ARR = 17; |
| |
| /** */ |
| static final byte STR = 18; |
| |
| /** */ |
| static final byte UUID = 19; |
| |
| /** */ |
| static final byte PROPS = 20; |
| |
| /** */ |
| static final byte ARRAY_LIST = 21; |
| |
| /** */ |
| static final byte HASH_MAP = 22; |
| |
| /** */ |
| static final byte HASH_SET = 23; |
| |
| /** */ |
| static final byte LINKED_LIST = 24; |
| |
| /** */ |
| static final byte LINKED_HASH_MAP = 25; |
| |
| /** */ |
| static final byte LINKED_HASH_SET = 26; |
| |
| /** */ |
| static final byte DATE = 27; |
| |
| /** */ |
| static final byte CLS = 28; |
| |
| /** */ |
| static final byte PROXY = 29; |
| |
| /** */ |
| static final byte ENUM = 100; |
| |
| /** */ |
| static final byte EXTERNALIZABLE = 101; |
| |
| /** */ |
| static final byte SERIALIZABLE = 102; |
| |
| /** UTF-8 character name. */ |
| static final Charset UTF_8 = Charset.forName("UTF-8"); |
| |
| static { |
| long mapOff; |
| |
| try { |
| mapOff = GridUnsafe.objectFieldOffset(HashSet.class.getDeclaredField("map")); |
| } |
| catch (NoSuchFieldException ignored) { |
| try { |
| // Workaround for legacy IBM JRE. |
| mapOff = GridUnsafe.objectFieldOffset(HashSet.class.getDeclaredField("backingMap")); |
| } |
| catch (NoSuchFieldException e2) { |
| throw new IgniteException("Initialization failure.", e2); |
| } |
| } |
| |
| HASH_SET_MAP_OFF = mapOff; |
| } |
| |
| /** |
| */ |
| private OptimizedMarshallerUtils() { |
| // No-op. |
| } |
| |
| /** |
| * Gets descriptor for provided class. |
| * |
| * @param clsMap Class descriptors by class map. |
| * @param cls Class. |
| * @param useCache True if class loader cache will be used, false otherwise. |
| * @param ctx Context. |
| * @param mapper ID mapper. |
| * @return Descriptor. |
| * @throws IOException In case of error. |
| */ |
| static OptimizedClassDescriptor classDescriptor( |
| ConcurrentMap<Class, OptimizedClassDescriptor> clsMap, |
| Class cls, |
| boolean useCache, |
| MarshallerContext ctx, |
| OptimizedMarshallerIdMapper mapper |
| ) throws IOException { |
| OptimizedClassDescriptor desc = clsMap.get(cls); |
| |
| if (desc == null && !useCache) { |
| desc = new OptimizedClassDescriptor(cls, resolveTypeId(cls.getName(), |
| mapper), clsMap, ctx, mapper, false); |
| } |
| else if (desc == null) { |
| int typeId = resolveTypeId(cls.getName(), mapper); |
| |
| boolean registered; |
| |
| try { |
| registered = ctx.registerClassName(JAVA_ID, typeId, cls.getName(), false); |
| } |
| catch (Exception e) { |
| throw new IOException("Failed to register class: " + cls.getName(), e); |
| } |
| |
| desc = new OptimizedClassDescriptor(cls, registered ? typeId : 0, clsMap, ctx, mapper); |
| |
| if (registered) { |
| OptimizedClassDescriptor old = clsMap.putIfAbsent(cls, desc); |
| |
| if (old != null) |
| desc = old; |
| } |
| } |
| |
| return desc; |
| } |
| |
| /** |
| * @param clsName Class name. |
| * @param mapper Mapper. |
| * @return Type ID. |
| */ |
| private static int resolveTypeId(String clsName, OptimizedMarshallerIdMapper mapper) { |
| int typeId; |
| |
| if (mapper != null) { |
| typeId = mapper.typeId(clsName); |
| |
| if (typeId == 0) |
| typeId = clsName.hashCode(); |
| } |
| else |
| typeId = clsName.hashCode(); |
| |
| return typeId; |
| } |
| |
| /** |
| * Gets descriptor for provided ID. |
| * |
| * @param clsMap Class descriptors by class map. |
| * @param typeId ID. |
| * @param ldr Class loader. |
| * @param useCache True if class loader cache will be used, false otherwise. |
| * @param ctx Context. |
| * @param mapper ID mapper. |
| * @return Descriptor. |
| * @throws IOException In case of error. |
| * @throws ClassNotFoundException If class was not found. |
| */ |
| static OptimizedClassDescriptor classDescriptor( |
| ConcurrentMap<Class, OptimizedClassDescriptor> clsMap, |
| int typeId, |
| ClassLoader ldr, |
| boolean useCache, |
| MarshallerContext ctx, |
| OptimizedMarshallerIdMapper mapper) throws IOException, ClassNotFoundException { |
| OptimizedClassDescriptor desc; |
| |
| if (useCache) |
| desc = descriptorFromCache(clsMap, typeId, ldr, ctx, mapper); |
| else |
| desc = descriptorWithoutCache(clsMap, typeId, ldr, ctx, mapper); |
| |
| return desc; |
| } |
| |
| /** |
| * @param clsMap Class descriptors by class map. |
| * @param typeId Type id. |
| * @param ldr Loader. |
| * @param ctx Context. |
| * @param mapper Mapper. |
| */ |
| @NotNull |
| private static OptimizedClassDescriptor descriptorWithoutCache(ConcurrentMap<Class, OptimizedClassDescriptor> clsMap, |
| int typeId, ClassLoader ldr, MarshallerContext ctx, |
| OptimizedMarshallerIdMapper mapper) throws ClassNotFoundException, IOException { |
| String clsName; |
| |
| try { |
| clsName = ctx.getClassName(JAVA_ID, typeId); |
| |
| if (clsName == null) |
| throw new ClassNotFoundException("Unknown type ID: " + typeId); |
| } |
| catch (IgniteCheckedException e) { |
| throw new IOException("Failed to resolve class for ID: " + typeId, e); |
| } |
| |
| Class cls = U.forName(clsName, ldr, null); |
| |
| OptimizedClassDescriptor desc = clsMap.get(cls); |
| |
| if (desc == null) |
| desc = new OptimizedClassDescriptor(cls, resolveTypeId(cls.getName(), |
| mapper), clsMap, ctx, mapper, false); |
| |
| return desc; |
| } |
| |
| /** |
| * @param clsMap Class descriptors by class map. |
| * @param typeId Type id. |
| * @param ldr Loader. |
| * @param ctx Context. |
| * @param mapper Mapper. |
| */ |
| @NotNull |
| private static OptimizedClassDescriptor descriptorFromCache(ConcurrentMap<Class, OptimizedClassDescriptor> clsMap, |
| int typeId, ClassLoader ldr, MarshallerContext ctx, |
| OptimizedMarshallerIdMapper mapper) throws ClassNotFoundException, IOException { |
| Class cls; |
| |
| try { |
| cls = ctx.getClass(typeId, ldr); |
| } |
| catch (IgniteCheckedException e) { |
| throw new IOException("Failed to resolve class for ID: " + typeId, e); |
| } |
| |
| OptimizedClassDescriptor desc = clsMap.get(cls); |
| |
| if (desc == null) { |
| OptimizedClassDescriptor old = clsMap.putIfAbsent(cls, |
| desc = new OptimizedClassDescriptor(cls, resolveTypeId(cls.getName(), mapper), clsMap, ctx, mapper)); |
| |
| if (old != null) |
| desc = old; |
| } |
| return desc; |
| } |
| |
| /** |
| * 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 short computeSerialVersionUid(Class cls, List<Field> fields) throws IOException { |
| if (Serializable.class.isAssignableFrom(cls) && !Enum.class.isAssignableFrom(cls)) { |
| try { |
| Field field = cls.getDeclaredField("serialVersionUID"); |
| |
| if (field.getType() == long.class) { |
| int mod = field.getModifiers(); |
| |
| if (Modifier.isStatic(mod) && Modifier.isFinal(mod)) { |
| field.setAccessible(true); |
| |
| return (short)field.getLong(null); |
| } |
| } |
| } |
| catch (NoSuchFieldException ignored) { |
| // No-op. |
| } |
| catch (IllegalAccessException e) { |
| throw new IOException(e); |
| } |
| |
| if (OptimizedMarshaller.USE_DFLT_SUID) |
| return (short)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 (short)hash; |
| } |
| |
| /** |
| * Gets byte field value. |
| * |
| * @param obj Object. |
| * @param off Field offset. |
| * @return Byte value. |
| */ |
| static byte getByte(Object obj, long off) { |
| return GridUnsafe.getByteField(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) { |
| GridUnsafe.putByteField(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 GridUnsafe.getShortField(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) { |
| GridUnsafe.putShortField(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 GridUnsafe.getIntField(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) { |
| GridUnsafe.putIntField(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 GridUnsafe.getLongField(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) { |
| GridUnsafe.putLongField(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 GridUnsafe.getFloatField(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) { |
| GridUnsafe.putFloatField(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 GridUnsafe.getDoubleField(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) { |
| GridUnsafe.putDoubleField(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 GridUnsafe.getCharField(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) { |
| GridUnsafe.putCharField(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 GridUnsafe.getBooleanField(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) { |
| GridUnsafe.putBooleanField(obj, off, val); |
| } |
| |
| /** |
| * Gets field value. |
| * |
| * @param obj Object. |
| * @param off Field offset. |
| * @return Value. |
| */ |
| static Object getObject(Object obj, long off) { |
| return GridUnsafe.getObjectField(obj, off); |
| } |
| |
| /** |
| * Sets field value. |
| * |
| * @param obj Object. |
| * @param off Field offset. |
| * @param val Value. |
| */ |
| static void setObject(Object obj, long off, Object val) { |
| GridUnsafe.putObjectField(obj, off, val); |
| } |
| } |