Java tutorial
/* * Copyright (C) 2016 Andrey Mogilev * * Licensed 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 com.gilecode.yagson.types; import com.gilecode.yagson.ReadContext; import com.gilecode.yagson.WriteContext; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.internal.$Gson$Types; import com.gilecode.yagson.adapters.AdapterUtils; import com.google.gson.internal.bind.MapTypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.lang.reflect.*; import java.util.*; import static java.util.Arrays.asList; /** * Collection of types-related utility methods. * * @author Andrey Mogilev */ public class TypeUtils { private static Map<String, Class<?>> primitiveWrappers = new HashMap<String, Class<?>>(); static { primitiveWrappers.put(boolean.class.getName(), Boolean.class); primitiveWrappers.put(byte.class.getName(), Byte.class); primitiveWrappers.put(short.class.getName(), Short.class); primitiveWrappers.put(int.class.getName(), Integer.class); primitiveWrappers.put(long.class.getName(), Long.class); primitiveWrappers.put(float.class.getName(), Float.class); primitiveWrappers.put(double.class.getName(), Double.class); } public static boolean typesDiffer(Type type, Class<?> actualClass) { if (type instanceof GenericArrayType && actualClass.isArray()) { return typesDiffer(((GenericArrayType) type).getGenericComponentType(), actualClass.getComponentType()); } else { return classesDiffer($Gson$Types.getRawType(type), actualClass); } } private static boolean classesDiffer(Class<?> declaredClass, Class<?> actualClass) { if (declaredClass == actualClass) { return false; } if (declaredClass.isPrimitive() || actualClass.isPrimitive()) { if (declaredClass.isPrimitive() && actualClass.isPrimitive()) { return true; } if (declaredClass.isPrimitive()) { return !actualClass.equals(primitiveWrappers.get(declaredClass.getName())); } else { return !declaredClass.equals(primitiveWrappers.get(actualClass.getName())); } } return true; } public static <T> T readTypeAdvisedValue(Gson context, JsonReader in, Type formalType, ReadContext ctx) throws IOException { Type advisedType = readTypeAdvice(in); Type type = TypeUtils.mergeTypes(advisedType, formalType); return readTypeAdvisedValueAfterType(context, in, ctx, type); } public static <T> T readTypeAdvisedValueAfterTypeField(Gson gson, JsonReader in, Type formalType, ReadContext ctx) throws IOException { Type advisedType = readTypeAdviceAfterTypeField(in); Type type = TypeUtils.mergeTypes(advisedType, formalType); return readTypeAdvisedValueAfterType(gson, in, ctx, type); } private static Type readTypeAdvice(JsonReader in) throws IOException { in.beginObject(); if (!in.hasNext()) { throw new JsonSyntaxException("BEGIN_OBJECT is not expected at path " + in.getPath()); } String name = in.nextName(); if (!name.equals("@type")) { throw new JsonSyntaxException("@type is expected at path " + in.getPath()); } return readTypeAdviceAfterTypeField(in); } private static final WildcardType unknownType = $Gson$Types.subtypeOf(Object.class); private static Type readTypeAdviceAfterTypeField(JsonReader in) throws IOException { // Check whether next tokens are type advise, fail if not String advisedTypeStr = in.nextString(); String advisedClassStr = advisedTypeStr; int i = advisedTypeStr.indexOf('<'); if (i >= 0) { if (!advisedTypeStr.endsWith(">")) { throw new JsonSyntaxException("Incorrect advised type: '" + advisedTypeStr + "'"); } advisedClassStr = advisedTypeStr.substring(0, i).trim(); String parametersStr = advisedTypeStr.substring(i + 1, advisedTypeStr.length() - 1).trim(); String[] parameters = parametersStr.split(","); Type[] parameterTypes = new Type[parameters.length]; boolean hasParameterTypes = false; for (int j = 0; j < parameters.length; j++) { Type type = toType(parameters[j].trim()); parameterTypes[j] = type; if (type != unknownType) { hasParameterTypes = true; } } if (hasParameterTypes) { Class advisedClass = (Class) toType(advisedClassStr); return $Gson$Types.newParameterizedTypeWithOwner(advisedClass.getEnclosingClass(), advisedClass, parameterTypes); } } return toType(advisedClassStr); } private static Type toType(String name) { if (name.equals("?")) { return unknownType; } try { return Class.forName(name); } catch (ClassNotFoundException e) { throw new JsonSyntaxException("Missing class specified in @type info", e); } } /** * Returns a class for name if it exists, and {@code null} otherwise. */ public static Class<?> safeClassForName(String name) { try { return Class.forName(name); } catch (ClassNotFoundException e) { return null; } } private static boolean consumeValueField(JsonReader in) throws IOException { if (!in.hasNext()) { // no @val means actually null value, e.g. skipped by serialization return false; } String name = in.nextName(); if (!name.equals("@val")) { throw new JsonSyntaxException("Only @type and @val fields are expected at the type advice " + "objects at path " + in.getPath()); } return true; } private static <T> T readTypeAdvisedValueAfterType(Gson gson, JsonReader in, ReadContext ctx, Type valueType) throws IOException { T result = null; if (consumeValueField(in)) { // use actual type adapter instead of delegate TypeAdapter<?> typeAdapter = gson.getAdapter(TypeToken.get(valueType)); result = (T) typeAdapter.read(in, ctx); } in.endObject(); return result; } /** * Determines whether the type information is necessary for successful de-serialization of the * object of the given actual class using the specified de-serialization type. * <p/> * Usually, the type information is <b>not</b> needed when the de-serialization type matches * the actual type, or if the actual type is <i>default</i> for the corresponding JSON representation. * * @param actualClass the actual class of the object being serialized * @param deserializationType the type which will be used for de-serialization, or {@code null} if not known * @param isMapKey whether the object is a key of some map * * @return whether the root type information is necessary */ public static boolean isTypeInfoRequired(Class<?> actualClass, Type deserializationType, boolean isMapKey) { boolean isEnumSet = EnumSet.class.isAssignableFrom(actualClass); boolean isEnumCollection = isEnumSet || EnumMap.class.isAssignableFrom(actualClass); if (isEnumCollection) { // for EnumSet and EnumMap additionally check that the enum element type is specified in the deserialization type // also, consider RegularEnumSet and JumboEnumSet to be the default deserializaton classes for EnumSet if (deserializationType == null) { return true; } Class<?> rawType = $Gson$Types.getRawType(deserializationType); Type enumType = isEnumSet ? $Gson$Types.getCollectionElementType(deserializationType, rawType) : $Gson$Types.getMapKeyAndValueTypes(deserializationType, rawType)[0]; if (enumType == null || !$Gson$Types.getRawType(enumType).isEnum()) { return true; } if (isEnumSet) { // EnumSet may be extended only by same-package classes, i.e. RegularEnumSet and JumboEnumSet // The implementation class is chosen automatically, so no specification required return !EnumSet.class.isAssignableFrom(rawType); } else { // EnumMap may potentially be extended return typesDiffer(deserializationType, actualClass); } } else if (isDefaultDeserializationClass(actualClass, deserializationType, isMapKey)) { return false; } else if (deserializationType == null) { return true; } else if (Enum.class.isAssignableFrom(actualClass) && $Gson$Types.getRawType(deserializationType).isEnum()) { // ignore synthetic enum classes generated for the enum values with overridden methods return false; } else { return typesDiffer(deserializationType, actualClass); } } @SuppressWarnings("unchecked") private static Set<Class<?>> generalDefaultClasses = new HashSet<Class<?>>(asList(String.class, Object.class, Double.class, double.class, Boolean.class, boolean.class, Long.class, long.class)); /** * Returns whether the actual class of an object is the default class for the corresponding * JSON representation, and so the type info may be skipped. */ private static boolean isDefaultDeserializationClass(Class<?> actualClass, Type deserializationType, boolean isMapKey) { // check if the class is default for all types if (isMapKey) { // as all keys are serialized as names, only actual string are default if (actualClass == String.class) { return true; } } else { if (generalDefaultClasses.contains(actualClass)) { return true; } } if (deserializationType == null) { return false; } if (isMapKey) { // the general long and double are default even for map keys only if the type is Number if (deserializationType == Number.class && (actualClass == Long.class || actualClass == Double.class)) { return true; } } else { // check some common collection cases (only the most common) // MUST be in sync with ConstructorConstructor.newDefaultImplementationConstructor() && ObjectTypeAdapter Class<?> rawType = $Gson$Types.getRawType(deserializationType); if (actualClass == ArrayList.class && (rawType == Object.class || rawType == Collection.class || rawType == List.class)) { return true; } } return false; } public static void writeTypeWrapperAndValue(JsonWriter out, Object value, TypeAdapter adapter, WriteContext ctx) throws IOException { Class<?> actualClass = value.getClass(); String parameterTypes = ""; if (EnumSet.class.isAssignableFrom(actualClass)) { Class<? extends Enum> enumClass = getField(enumSetElementTypeField, value); // change the written class to EnumSet (from RegularEnumSet/JumboEnumSet) actualClass = EnumSet.class; // write parameter for EnumSet type parameterTypes = "<" + enumClass.getName() + ">"; // replace the used adapter for the one with known element type Type enumSetType = $Gson$Types.newParameterizedTypeWithOwner(null, actualClass, enumClass); adapter = ctx.getGson().getAdapter(TypeToken.get(enumSetType)); } else if (EnumMap.class.isAssignableFrom(actualClass)) { Class<? extends Enum> enumClass = getField(enumMapKeyTypeField, value); int mapKeyTypeVarIdx; TypeVariable[] actualClassTypeVariables = actualClass.getTypeParameters(); if (EnumMap.class.equals(actualClass)) { mapKeyTypeVarIdx = 0; parameterTypes = "<" + enumClass.getName() + ",?>"; } else { // the parameters may be overridden in subclasses // print the parameters, only if the enum (key) type is still present in the parameters lists of the actuall class TypeVariable mapKeyTypeVar = EnumMap.class.getTypeParameters()[0]; mapKeyTypeVarIdx = indexOfInheritedTypeVariable(mapKeyTypeVar, EnumMap.class, actualClass); if (mapKeyTypeVarIdx >= 0) { StringBuilder sb = new StringBuilder(); sb.append("<"); for (int i = 0; i < actualClassTypeVariables.length; i++) { if (i == mapKeyTypeVarIdx) { sb.append(enumClass.getName()); } else { sb.append('?'); } sb.append(','); } sb.setCharAt(sb.length() - 1, '>'); // delete last ',' and add '>' parameterTypes = sb.toString(); } } if (mapKeyTypeVarIdx >= 0) { TypeAdapter resolvedAdapter = AdapterUtils.resolve(adapter, value); if (resolvedAdapter instanceof MapTypeAdapterFactory.Adapter) { Type formalMapType = ((MapTypeAdapterFactory.Adapter) resolvedAdapter).getFormalMapType(); Type[] typeArgsWithExactKeyType = new Type[actualClassTypeVariables.length]; Arrays.fill(typeArgsWithExactKeyType, unknownType); typeArgsWithExactKeyType[mapKeyTypeVarIdx] = enumClass; Type mergedMapType = mergeTypes($Gson$Types.newParameterizedTypeWithOwner( actualClass.getEnclosingClass(), actualClass, typeArgsWithExactKeyType), formalMapType); adapter = ctx.getGson().getAdapter(TypeToken.get(mergedMapType)); } } // else key type is already fixed in subtypes, so do not print params and do not update adapter } // do not print type wrapper if the value is known to be skipped boolean isValueSkipped = AdapterUtils.isSkipSerializeTypeAdapter(adapter); if (!isValueSkipped) { out.beginObject(); out.name("@type"); out.value(actualClass.getName() + parameterTypes); out.name("@val"); } adapter.write(out, value, ctx); if (!isValueSkipped) { out.endObject(); } } static Field getDeclaredField(Class declaringClass, String fieldName) { try { Field f = declaringClass.getDeclaredField(fieldName); f.setAccessible(true); return f; } catch (NoSuchFieldException e) { throw new IllegalStateException("Field '" + fieldName + "' is not found in " + declaringClass, e); } } /** * Gets field by name, declared in the class or its superclasses. */ public static Field findField(Class c, String fieldName) { while (c != null) { for (Field f : c.getDeclaredFields()) { if (fieldName.equals(f.getName())) { // found f.setAccessible(true); return f; } } c = c.getSuperclass(); } return null; } @SuppressWarnings("unchecked") public static <T> T getField(Field f, Object instance) { try { return (T) f.get(instance); } catch (IllegalAccessException e) { throw new IllegalStateException("Failed to obtain the value of field " + f + " from object " + instance, e); } } private static final Field enumSetElementTypeField = getDeclaredField(EnumSet.class, "elementType"); private static final Field enumMapKeyTypeField = getDeclaredField(EnumMap.class, "keyType"); /** * Returns whether the provided class is the general class (i.e. not interface, enum, array or primitive) and * is not abstract. */ public static boolean isGeneralNonAbstractClass(Class<?> c) { return c != null && !c.isInterface() && !c.isArray() && !c.isEnum() && !c.isPrimitive() && !Modifier.isAbstract(c.getModifiers()); } /** * Find in the given class and all its superclasses all non-static fields assignable to the specified * classes to find, except of the specified 'except' classes, and returns such fields as a list. * * @param c the class to check * @param allowTransient whether to check transient fields * @param max the max number of the fields to return, or 0 if unlimited * @param fieldClassesToFind the field classes to find * @param exceptClasses the field classes to skip from search * * @return all fields of the supposed field classes */ private static List<Field> findFields(Class<?> c, boolean allowTransient, int max, Iterable<Class<?>> fieldClassesToFind, Iterable<Class<?>> exceptClasses) { List<Field> result = new ArrayList<Field>(max > 0 && max < 10 ? max : 10); while (c != Object.class && c != null) { for (Field field : c.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) { continue; } if (!allowTransient && Modifier.isTransient(field.getModifiers())) { continue; } findClassesLoop: for (Class<?> fc : fieldClassesToFind) { if (fc.isAssignableFrom(field.getType())) { if (exceptClasses != null) { for (Class ec : exceptClasses) { if (ec == field.getType()) { continue findClassesLoop; } } } result.add(field); break; } } } c = c.getSuperclass(); // TODO: maybe also check enclosing class for non-static local classes, but it complicates reflective gets } return result; } /** * Find in the given class and all its superclasses all fields assignable to the specified * classes to find, and returns such fields as a list. * * @param c the class to check * @param fieldClassesToFind the field classes to find * @param exceptClasses the field classes to skip from search * @return all fields of the supposed field classes */ public static List<Field> findFields(Class<?> c, boolean allowTransient, Iterable<Class<?>> fieldClassesToFind, Iterable<Class<?>> exceptClasses) { return findFields(c, allowTransient, 0, fieldClassesToFind, exceptClasses); } static Field findOneFieldByType(Class<?> c, Class<?> fieldClassToFind) { List<Class<?>> fieldClassesToFind = Collections.<Class<?>>singletonList(fieldClassToFind); List<Field> found = findFields(c, true, 1, fieldClassesToFind, null); if (found.size() > 0) { Field foundField = found.get(0); foundField.setAccessible(true); return foundField; } return null; } /** * Returns whether the class or one of its superclasses contains a non-static field assignable to * one of the specified 'find' classes, but except of the specified 'except' classes. * * @param c the class to check * @param allowTransient whether to check transient fields * @param fieldClassesToFind the field classes to find * @param exceptClasses the field classes to skip from search * @return if any field of the class to check belongs to any of the supposed field classes, return {@code true} */ public static boolean containsField(Class<?> c, boolean allowTransient, Iterable<Class<?>> fieldClassesToFind, Iterable<Class<?>> exceptClasses) { List<Field> found = findFields(c, allowTransient, 1, fieldClassesToFind, exceptClasses); return !found.isEmpty(); } /** * Returns whether the class overrides the specified method, i.e. the class or one of its superclasses until * the declaring class of the method, contains the method with the same signature. * * @param c the class to check * @param m the method to check * * @return if the method is overridden */ public static boolean isOverridden(Class<?> c, Method m) throws NoSuchMethodException { Method foundMethod = c.getMethod(m.getName(), m.getParameterTypes()); return foundMethod != null && !m.equals(foundMethod) && !Modifier.isAbstract(foundMethod.getModifiers()) && m.getDeclaringClass().isAssignableFrom(foundMethod.getDeclaringClass()); } /** * Merges the exact raw type and optional parameter types information from the formal type (which should be * same type or one of the supertypes) into a single type. * If the parameter types are present, the resulting type is {@link ParameterizedType}, otherwise it is the * raw type (i.e. Class). * <p/> * The raw type is always taken from rawTypeSource, the parameter types may be taken from both sources and * then merged. * <p/> * Used to enrich the raw class (mostly from advised type information) with the type parameters available in * the formal deserizalization type. For example, if the advised type is {@code HashMap.class} and the formal * type is {@code Map<String, String>}, this method shall create and return the type of * {@code HashMap<String, String>} * * @param rawTypeSource the source of the raw class type, with optional parameter types * @param parametersTypesSource the type which is used as a source of the parameter types * * @return the result of merging both types, with a raw type equal to {@code rawTypeSource.} which raw tyraw type enriched with the parameter types obtained from the super-type if possible, or * the rawType otherwise */ public static Type mergeTypes(Type rawTypeSource, Type parametersTypesSource) { if (parametersTypesSource == null || !(parametersTypesSource instanceof ParameterizedType)) { return rawTypeSource; } Class rawType = $Gson$Types.getRawType(rawTypeSource); TypeVariable<?>[] typeParameters = rawType.getTypeParameters(); if (typeParameters.length == 0) { // no parameter types return rawTypeSource; } ParameterizedType parametersSource = (ParameterizedType) parametersTypesSource; Class<?> parametersSourceRawType = $Gson$Types.getRawType(parametersSource.getRawType()); if (!parametersSourceRawType.isAssignableFrom(rawType)) { // illegal use - superType is not a super type for the raw type return rawTypeSource; } if (rawTypeSource == parametersSourceRawType) { return parametersTypesSource; } // if rawTypeSource is ParameterizedType too, use it as an additional parameters source ParameterizedType extraParametersSource = rawTypeSource instanceof ParameterizedType ? (ParameterizedType) rawTypeSource : null; Type[] resolvedTypeArgs = new Type[typeParameters.length]; for (int i = 0; i < typeParameters.length; i++) { TypeVariable<?> typeVar = typeParameters[i]; Collection<Type> lookupSuperTypes = getGenericSuperTypes(rawType); Type resolvedType = lookupTypeArg(typeVar, lookupSuperTypes, parametersSource); if (extraParametersSource != null && (resolvedType == null || resolvedType instanceof WildcardType || resolvedType instanceof TypeVariable)) { // prefer alternative parameters source resolvedType = extraParametersSource.getActualTypeArguments()[i]; } resolvedTypeArgs[i] = resolvedType == null ? typeVar : resolvedType; } return $Gson$Types.newParameterizedTypeWithOwner(rawType.getEnclosingClass(), rawType, resolvedTypeArgs); } private static Collection<Type> getGenericSuperTypes(Class<?> rawType) { Type[] genericInterfaces = rawType.getGenericInterfaces(); Type genericSuperclass = rawType.getGenericSuperclass(); List<Type> superTypes = new ArrayList<Type>(genericInterfaces.length + 1); if (genericSuperclass != null) { superTypes.add(genericSuperclass); } superTypes.addAll(asList(genericInterfaces)); return superTypes; } public static int indexOfInheritedTypeVariable(TypeVariable superTypeVar, Class rawSuperType, Class inheritedClass) { // build the inheritance chain of supertypes, ending on the generic version of supertype List<Type> inheritanceChain = new ArrayList<Type>(); fillGenericInheritanceChain(inheritanceChain, rawSuperType, inheritedClass); // iterate the chain in the reverse order, matching the interested type variable on each step Collections.reverse(inheritanceChain); TypeVariable var = superTypeVar; for (Type t : inheritanceChain) { if (!(t instanceof ParameterizedType)) { return -1; } TypeVariable[] typeVariables = $Gson$Types.getRawType(t).getTypeParameters(); int matchedVarIdx = findTypeVariableIndex(var, typeVariables); assert matchedVarIdx >= 0 : "Missing typeVar " + var + " in " + t; Type[] typeArguments = ((ParameterizedType) t).getActualTypeArguments(); Type matchedTypeArg = typeArguments[matchedVarIdx]; if (!(matchedTypeArg instanceof TypeVariable)) { // resolved to non-variable, hence not present in the inherited class return -1; } // continue on the one type level down var = (TypeVariable) matchedTypeArg; } return findTypeVariableIndex(var, inheritedClass.getTypeParameters()); } private static int findTypeVariableIndex(TypeVariable typeVarToFind, TypeVariable[] typeVariables) { for (int i = 0; i < typeVariables.length; i++) { if (typeVarToFind == typeVariables[i]) { return i; } } return -1; } @SuppressWarnings("unchecked") private static void fillGenericInheritanceChain(List<Type> inheritanceChain, Class superType, Class inheritedClass) { for (Type t : getGenericSuperTypes(inheritedClass)) { Class c = $Gson$Types.getRawType(t); if (superType.isAssignableFrom(c)) { inheritanceChain.add(t); if (c != superType) { fillGenericInheritanceChain(inheritanceChain, superType, c); } break; } } } private static Type lookupTypeArg(TypeVariable<?> typeVar, Collection<Type> lookupSuperTypes, ParameterizedType knownParameterizedSuperType) { for (Type lookupType : lookupSuperTypes) { if (lookupType instanceof ParameterizedType) { Type[] typeArguments = ((ParameterizedType) lookupType).getActualTypeArguments(); int foundIdx = -1; for (int i = 0; i < typeArguments.length; i++) { if (typeVar == typeArguments[i]) { foundIdx = i; break; } } if (foundIdx < 0) { // not matched to any of type parameters of this lookup super-type, try next continue; } Class<?> rawLookupType = $Gson$Types.getRawType(lookupType); if (rawLookupType == $Gson$Types.getRawType(knownParameterizedSuperType)) { // matched up to the original parameterized super-type, use it return knownParameterizedSuperType.getActualTypeArguments()[foundIdx]; } // else continue lookup on a higher level Type resolved = lookupTypeArg(rawLookupType.getTypeParameters()[foundIdx], getGenericSuperTypes(rawLookupType), knownParameterizedSuperType); if (resolved != null) { return resolved; } } } return null; } /** * Checks whether the input string would be treated as a long by the Gson's parsing algorithms, * see {@link JsonReader#peekNumber()}. The current rules are: the string should contain only digits and * optional sign, with no decimal point or exponents, and the resulting value should fit to long range. * <p/> * If approved to be treated as long, parses and returns the number; otherwise, returns {@code null} */ public static Long parseNumberIfLong(String str) { if (str.indexOf('.') >= 0 || str.indexOf('E') >= 0 || str.indexOf('e') >= 0) { return null; } try { return Long.parseLong(str); } catch (NumberFormatException e) { return null; } } public static EmitTypeInfoPredicate TYPE_INFO_SKIP = new EmitTypeInfoPredicate() { public boolean apply(Class<?> actualClass, Type formalType) { return false; } }; private static EmitTypeInfoPredicate TYPE_INFO_GENERAL_RULE = new EmitTypeInfoPredicate() { public boolean apply(Class<?> actualClass, Type formalType) { return isTypeInfoRequired(actualClass, formalType, false); } }; private static EmitTypeInfoPredicate TYPE_INFO_MAP_KEY_RULE = new EmitTypeInfoPredicate() { public boolean apply(Class<?> actualClass, Type formalType) { return isTypeInfoRequired(actualClass, formalType, true); } }; public static EmitTypeInfoPredicate getEmitTypeInfoRule(Gson context, boolean isMapKey) { return !context.getTypeInfoPolicy().isEnabled() ? TYPE_INFO_SKIP : (isMapKey ? TYPE_INFO_MAP_KEY_RULE : TYPE_INFO_GENERAL_RULE); } public static Iterable<Class<?>> classOf(Class<?> c) { return Collections.<Class<?>>singleton(c); } public static Iterable<Class<?>> classes(Class<?>... classes) { return asList(classes); } public static void initEnumMapKeyType(EnumMap<?, ?> instance, Class<?> keyType) { if (!keyType.isEnum()) { throw new JsonSyntaxException("Only enum keys are allowed for EnumMap, but got " + keyType); } EnumMap otherInstance = new EnumMap(keyType); copyFields(instance, otherInstance, EnumMap.class, "keyType", "keyUniverse", "vals"); } private static void copyFields(Object to, Object from, Class<?> declaringClass, String... fieldNames) { for (String fname : fieldNames) { try { Field f = declaringClass.getDeclaredField(fname); f.setAccessible(true); f.set(to, f.get(from)); } catch (Exception e) { throw new IllegalStateException("Failed to initialize field " + fname + " of " + declaringClass); } } } /** * Returns whether the specified type is not null, and is a class of a primitive number type or one of * the wrapper Number classes. */ public static boolean isNumberType(Type type) { if (type instanceof Class<?>) { Class<?> c = (Class<?>) type; if (primitiveWrappers.containsKey(c.getName())) { return !c.equals(boolean.class); } else if (primitiveWrappers.containsValue(c)) { return !c.equals(Boolean.class); } } return false; } /** * Converts a number to another number type. Throws {@link ClassCastException} if such conversion fails, or * 'anotherNumberType' is not actually a number type. */ public static Number convertNumber(Number src, Type anotherNumberType) { if (anotherNumberType instanceof Class<?>) { Class<?> cl = (Class<?>) anotherNumberType; if (primitiveWrappers.containsKey(cl.getName())) { cl = primitiveWrappers.get(cl.getName()); } Class<? extends Number> anotherNumberClass = cl.asSubclass(Number.class); if (anotherNumberClass == Float.class) { return src.floatValue(); } else if (anotherNumberClass == Double.class) { return src.doubleValue(); } else if (anotherNumberClass == Byte.class) { return src.byteValue(); } else if (anotherNumberClass == Short.class) { return src.shortValue(); } else if (anotherNumberClass == Integer.class) { return src.intValue(); } else if (anotherNumberClass == Long.class) { return src.longValue(); } } throw new ClassCastException("Failed to convert " + src.getClass() + " to " + anotherNumberType); } }