/* * Copyright (c) 2011 Google Inc. * * 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.google.api.client.util; import java.lang.reflect.Array; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; /** * Utilities for working with key/value data based on the {@link Key} annotation. * * @since 1.4 * @author Yaniv Inbar */ public class Data { // NOTE: create new instances to avoid cache, e.g. new String() /** The single instance of the magic null object for a {@link Boolean}. */ public static final Boolean NULL_BOOLEAN = new Boolean(true); /** The single instance of the magic null object for a {@link String}. */ public static final String NULL_STRING = new String(); /** The single instance of the magic null object for a {@link Character}. */ public static final Character NULL_CHARACTER = new Character((char) 0); /** The single instance of the magic null object for a {@link Byte}. */ public static final Byte NULL_BYTE = new Byte((byte) 0); /** The single instance of the magic null object for a {@link Short}. */ public static final Short NULL_SHORT = new Short((short) 0); /** The single instance of the magic null object for a {@link Integer}. */ public static final Integer NULL_INTEGER = new Integer(0); /** The single instance of the magic null object for a {@link Float}. */ public static final Float NULL_FLOAT = new Float(0); /** The single instance of the magic null object for a {@link Long}. */ public static final Long NULL_LONG = new Long(0); /** The single instance of the magic null object for a {@link Double}. */ public static final Double NULL_DOUBLE = new Double(0); /** The single instance of the magic null object for a {@link BigInteger}. */ public static final BigInteger NULL_BIG_INTEGER = new BigInteger("0"); /** The single instance of the magic null object for a {@link BigDecimal}. */ public static final BigDecimal NULL_BIG_DECIMAL = new BigDecimal("0"); /** The single instance of the magic null object for a {@link DateTime}. */ public static final DateTime NULL_DATE_TIME = new DateTime(0); /** Cache of the magic null object for the given Java class. */ private static final ConcurrentHashMap<Class<?>, Object> NULL_CACHE = new ConcurrentHashMap<Class<?>, Object>(); static { // special cases for some primitives NULL_CACHE.put(Boolean.class, NULL_BOOLEAN); NULL_CACHE.put(String.class, NULL_STRING); NULL_CACHE.put(Character.class, NULL_CHARACTER); NULL_CACHE.put(Byte.class, NULL_BYTE); NULL_CACHE.put(Short.class, NULL_SHORT); NULL_CACHE.put(Integer.class, NULL_INTEGER); NULL_CACHE.put(Float.class, NULL_FLOAT); NULL_CACHE.put(Long.class, NULL_LONG); NULL_CACHE.put(Double.class, NULL_DOUBLE); NULL_CACHE.put(BigInteger.class, NULL_BIG_INTEGER); NULL_CACHE.put(BigDecimal.class, NULL_BIG_DECIMAL); NULL_CACHE.put(DateTime.class, NULL_DATE_TIME); } /** * Returns the single instance of the magic object that represents the "null" value for the given * Java class (including array or enum). * * @param objClass class of the object needed * @return magic object instance that represents the "null" value (not Java {@code null}) * @throws IllegalArgumentException if unable to create a new instance */ public static <T> T nullOf(Class<?> objClass) { Object result = NULL_CACHE.get(objClass); if (result == null) { synchronized (NULL_CACHE) { result = NULL_CACHE.get(objClass); if (result == null) { if (objClass.isArray()) { // arrays are special because we need to compute both the dimension and component type int dims = 0; Class<?> componentType = objClass; do { componentType = componentType.getComponentType(); dims++; } while (componentType.isArray()); result = Array.newInstance(componentType, new int[dims]); } else if (objClass.isEnum()) { // enum requires look for constant with @NullValue FieldInfo fieldInfo = ClassInfo.of(objClass).getFieldInfo(null); Preconditions.checkNotNull( fieldInfo, "enum missing constant with @NullValue annotation: %s", objClass); @SuppressWarnings({"unchecked", "rawtypes"}) Enum e = fieldInfo.<Enum>enumValue(); result = e; } else { // other classes are simpler result = Types.newInstance(objClass); } NULL_CACHE.put(objClass, result); } } } @SuppressWarnings("unchecked") T tResult = (T) result; return tResult; } /** * Returns whether the given object is the magic object that represents the null value of its * class. * * @param object object or {@code null} * @return whether it is the magic null value or {@code false} for {@code null} input */ public static boolean isNull(Object object) { // don't call nullOf because will throw IllegalArgumentException if cannot create instance return object != null && object == NULL_CACHE.get(object.getClass()); } /** * Returns the map to use for the given data that is treated as a map from string key to some * value. * * <p> * If the input is {@code null}, it returns an empty map. If the input is a map, it simply returns * the input. Otherwise, it will create a map view using reflection that is backed by the object, * so that any changes to the map will be reflected on the object. The map keys of that map view * are based on the {@link Key} annotation, and null is not a possible map value, although the * magic null instance is possible (see {@link #nullOf(Class)} and {@link #isNull(Object)}). * Iteration order of the data keys is based on the sorted (ascending) key names of the declared * fields. Note that since the map view is backed by the object, and that the object may change, * many methods in the map view must recompute the field values using reflection, for example * {@link Map#size()} must check the number of non-null fields. * </p> * * @param data any key value data, represented by an object or a map, or {@code null} * @return key/value map to use */ public static Map<String, Object> mapOf(Object data) { if (data == null || isNull(data)) { return Collections.emptyMap(); } if (data instanceof Map<?, ?>) { @SuppressWarnings("unchecked") Map<String, Object> result = (Map<String, Object>) data; return result; } Map<String, Object> result = new DataMap(data, false); return result; } /** * Returns a deep clone of the given key/value data, such that the result is a completely * independent copy. * <p> * This should not be used directly in the implementation of {@code Object.clone()}. Instead use * {@link #deepCopy(Object, Object)} for that purpose. * </p> * <p> * Final fields cannot be changed and therefore their value won't be copied. * </p> * * @param data key/value data object or map to clone or {@code null} for a {@code null} return * value * @return deep clone or {@code null} for {@code null} input */ @SuppressWarnings("unchecked") public static <T> T clone(T data) { // don't need to clone primitive if (data == null || Data.isPrimitive(data.getClass())) { return data; } if (data instanceof GenericData) { return (T) ((GenericData) data).clone(); } T copy; Class<?> dataClass = data.getClass(); if (dataClass.isArray()) { copy = (T) Array.newInstance(dataClass.getComponentType(), Array.getLength(data)); } else if (data instanceof ArrayMap<?, ?>) { copy = (T) ((ArrayMap<?, ?>) data).clone(); } else { copy = (T) Types.newInstance(dataClass); } deepCopy(data, copy); return copy; } /** * Makes a deep copy of the given source object into the destination object that is assumed to be * constructed using {@code Object.clone()}. * * <p> * Example usage of this method in {@code Object.clone()}: * </p> * * <pre> @Override public MyObject clone() { try { @SuppressWarnings("unchecked") MyObject result = (MyObject) super.clone(); Data.deepCopy(this, result); return result; } catch (CloneNotSupportedException e) { throw new IllegalStateException(e); } } * </pre> * <p> * Final fields cannot be changed and therefore their value won't be copied. * </p> * * @param src source object * @param dest destination object of identical type as source object, and any contained arrays * must be the same length */ public static void deepCopy(Object src, Object dest) { Class<?> srcClass = src.getClass(); Preconditions.checkArgument(srcClass == dest.getClass()); if (srcClass.isArray()) { // clone array Preconditions.checkArgument(Array.getLength(src) == Array.getLength(dest)); int index = 0; for (Object value : Types.iterableOf(src)) { Array.set(dest, index++, clone(value)); } } else if (Collection.class.isAssignableFrom(srcClass)) { // clone collection @SuppressWarnings("unchecked") Collection<Object> srcCollection = (Collection<Object>) src; if (ArrayList.class.isAssignableFrom(srcClass)) { @SuppressWarnings("unchecked") ArrayList<Object> destArrayList = (ArrayList<Object>) dest; destArrayList.ensureCapacity(srcCollection.size()); } @SuppressWarnings("unchecked") Collection<Object> destCollection = (Collection<Object>) dest; for (Object srcValue : srcCollection) { destCollection.add(clone(srcValue)); } } else { // clone generic data or a non-map Object boolean isGenericData = GenericData.class.isAssignableFrom(srcClass); if (isGenericData || !Map.class.isAssignableFrom(srcClass)) { ClassInfo classInfo = isGenericData ? ((GenericData) src).classInfo : ClassInfo.of(srcClass); for (String fieldName : classInfo.names) { FieldInfo fieldInfo = classInfo.getFieldInfo(fieldName); // skip final fields if (!fieldInfo.isFinal()) { // generic data already has primitive types copied by clone() if (!isGenericData || !fieldInfo.isPrimitive()) { Object srcValue = fieldInfo.getValue(src); if (srcValue != null) { fieldInfo.setValue(dest, clone(srcValue)); } } } } } else if (ArrayMap.class.isAssignableFrom(srcClass)) { // clone array map @SuppressWarnings("unchecked") ArrayMap<Object, Object> destMap = (ArrayMap<Object, Object>) dest; @SuppressWarnings("unchecked") ArrayMap<Object, Object> srcMap = (ArrayMap<Object, Object>) src; int size = srcMap.size(); for (int i = 0; i < size; i++) { Object srcValue = srcMap.getValue(i); destMap.set(i, clone(srcValue)); } } else { // clone map @SuppressWarnings("unchecked") Map<String, Object> destMap = (Map<String, Object>) dest; @SuppressWarnings("unchecked") Map<String, Object> srcMap = (Map<String, Object>) src; for (Map.Entry<String, Object> srcEntry : srcMap.entrySet()) { destMap.put(srcEntry.getKey(), clone(srcEntry.getValue())); } } } } /** * Returns whether the given type is one of the supported primitive classes like number and * date/time, or is a wildcard of one. * * <p> * A primitive class is any class for whom {@link Class#isPrimitive()} is true, as well as any * classes of type: {@link Character}, {@link String}, {@link Integer}, {@link Long}, * {@link Short}, {@link Byte}, {@link Float}, {@link Double}, {@link BigInteger}, * {@link BigDecimal}, {@link Boolean}, and {@link DateTime}. * </p> * * @param type type or {@code null} for {@code false} result * @return whether it is a primitive */ public static boolean isPrimitive(Type type) { // TODO(yanivi): support java.net.URI as primitive type? if (type instanceof WildcardType) { type = Types.getBound((WildcardType) type); } if (!(type instanceof Class<?>)) { return false; } Class<?> typeClass = (Class<?>) type; return typeClass.isPrimitive() || typeClass == Character.class || typeClass == String.class || typeClass == Integer.class || typeClass == Long.class || typeClass == Short.class || typeClass == Byte.class || typeClass == Float.class || typeClass == Double.class || typeClass == BigInteger.class || typeClass == BigDecimal.class || typeClass == DateTime.class || typeClass == Boolean.class; } /** * Returns whether to given value is {@code null} or its class is primitive as defined by * {@link Data#isPrimitive(Type)}. */ public static boolean isValueOfPrimitiveType(Object fieldValue) { return fieldValue == null || Data.isPrimitive(fieldValue.getClass()); } /** * Parses the given string value based on the given primitive type. * <p> * Types are parsed as follows: * </p> * <ul> * <li>{@link Void}: null</li> * <li>{@code null} or is assignable from {@link String} (like {@link Object}): no parsing</li> * <li>{@code char} or {@link Character}: {@link String#charAt(int) String.charAt}(0) (requires * length to be exactly 1)</li> * <li>{@code boolean} or {@link Boolean}: {@link Boolean#valueOf(String)}</li> * <li>{@code byte} or {@link Byte}: {@link Byte#valueOf(String)}</li> * <li>{@code short} or {@link Short}: {@link Short#valueOf(String)}</li> * <li>{@code int} or {@link Integer}: {@link Integer#valueOf(String)}</li> * <li>{@code long} or {@link Long}: {@link Long#valueOf(String)}</li> * <li>{@code float} or {@link Float}: {@link Float#valueOf(String)}</li> * <li>{@code double} or {@link Double}: {@link Double#valueOf(String)}</li> * <li>{@link BigInteger}: {@link BigInteger#BigInteger(String) BigInteger(String)}</li> * <li>{@link BigDecimal}: {@link BigDecimal#BigDecimal(String) BigDecimal(String)}</li> * <li>{@link DateTime}: {@link DateTime#parseRfc3339(String)}</li> * </ul> * * <p> * Note that this may not be the right behavior for some use cases. * </p> * * @param type primitive type or {@code null} to parse as a string * @param stringValue string value to parse or {@code null} for {@code null} result * @return parsed object or {@code null} for {@code null} input * @throws IllegalArgumentException if the given class is not a primitive class */ public static Object parsePrimitiveValue(Type type, String stringValue) { Class<?> primitiveClass = type instanceof Class<?> ? (Class<?>) type : null; if (type == null || primitiveClass != null) { if (primitiveClass == Void.class) { return null; } if (stringValue == null || primitiveClass == null || primitiveClass.isAssignableFrom(String.class)) { return stringValue; } if (primitiveClass == Character.class || primitiveClass == char.class) { if (stringValue.length() != 1) { throw new IllegalArgumentException( "expected type Character/char but got " + primitiveClass); } return stringValue.charAt(0); } if (primitiveClass == Boolean.class || primitiveClass == boolean.class) { return Boolean.valueOf(stringValue); } if (primitiveClass == Byte.class || primitiveClass == byte.class) { return Byte.valueOf(stringValue); } if (primitiveClass == Short.class || primitiveClass == short.class) { return Short.valueOf(stringValue); } if (primitiveClass == Integer.class || primitiveClass == int.class) { return Integer.valueOf(stringValue); } if (primitiveClass == Long.class || primitiveClass == long.class) { return Long.valueOf(stringValue); } if (primitiveClass == Float.class || primitiveClass == float.class) { return Float.valueOf(stringValue); } if (primitiveClass == Double.class || primitiveClass == double.class) { return Double.valueOf(stringValue); } if (primitiveClass == DateTime.class) { return DateTime.parseRfc3339(stringValue); } if (primitiveClass == BigInteger.class) { return new BigInteger(stringValue); } if (primitiveClass == BigDecimal.class) { return new BigDecimal(stringValue); } if (primitiveClass.isEnum()) { @SuppressWarnings({"unchecked", "rawtypes"}) Enum result = ClassInfo.of(primitiveClass).getFieldInfo(stringValue).<Enum>enumValue(); return result; } } throw new IllegalArgumentException("expected primitive class, but got: " + type); } /** * Returns a new collection instance for the given type. * <p> * Creates a new collection instance specified for the first input collection class that matches * as follows: * <ul> * <li>{@code null} or an array or assignable from {@link ArrayList} (like {@link List} or * {@link Collection} or {@link Object}): returns an {@link ArrayList}</li> * <li>assignable from {@link HashSet}: returns a {@link HashSet}</li> * <li>assignable from {@link TreeSet}: returns a {@link TreeSet}</li> * <li>else: calls {@link Types#newInstance(Class)}</li> * </ul> * * @param type type or {@code null} for {@link ArrayList}. * @return new collection instance * @throws ClassCastException if result is does not extend {@link Collection} */ public static Collection<Object> newCollectionInstance(Type type) { if (type instanceof WildcardType) { type = Types.getBound((WildcardType) type); } if (type instanceof ParameterizedType) { type = ((ParameterizedType) type).getRawType(); } Class<?> collectionClass = type instanceof Class<?> ? (Class<?>) type : null; if (type == null || type instanceof GenericArrayType || collectionClass != null && (collectionClass.isArray() || collectionClass.isAssignableFrom(ArrayList.class))) { return new ArrayList<Object>(); } if (collectionClass == null) { throw new IllegalArgumentException("unable to create new instance of type: " + type); } if (collectionClass.isAssignableFrom(HashSet.class)) { return new HashSet<Object>(); } if (collectionClass.isAssignableFrom(TreeSet.class)) { return new TreeSet<Object>(); } @SuppressWarnings("unchecked") Collection<Object> result = (Collection<Object>) Types.newInstance(collectionClass); return result; } /** * Returns a new instance of a map based on the given field class. * <p> * Creates a new map instance specified for the first input map class that matches as follows: * </p> * <ul> * <li>{@code null} or assignable from {@link ArrayMap} (like {@link Map} or {@link Object}): * returns an {@link ArrayMap}</li> * <li>assignable from {@link TreeMap} (like {@link SortedMap}): returns a {@link TreeMap}</li> * <li>else: calls {@link Types#newInstance(Class)}</li> * </ul> * * @param mapClass field class * @throws ClassCastException if result is does not extend {@link Map} */ public static Map<String, Object> newMapInstance(Class<?> mapClass) { if (mapClass == null || mapClass.isAssignableFrom(ArrayMap.class)) { return ArrayMap.create(); } if (mapClass.isAssignableFrom(TreeMap.class)) { return new TreeMap<String, Object>(); } @SuppressWarnings("unchecked") Map<String, Object> result = (Map<String, Object>) Types.newInstance(mapClass); return result; } /** * Aggressively resolves the given type in such a way that the resolved type is not a wildcard * type or a type variable, returning {@code Object.class} if the type variable cannot be * resolved. * * @param context context list, ordering from least specific to most specific type context, for * example container class and then its field * @param type type or {@code null} for {@code null} result * @return resolved type (which may be class, parameterized type, or generic array type, but not * wildcard type or type variable) or {@code null} for {@code null} input */ public static Type resolveWildcardTypeOrTypeVariable(List<Type> context, Type type) { // first deal with a wildcard, e.g. ? extends Number if (type instanceof WildcardType) { type = Types.getBound((WildcardType) type); } // next deal with a type variable T while (type instanceof TypeVariable<?>) { // resolve the type variable Type resolved = Types.resolveTypeVariable(context, (TypeVariable<?>) type); if (resolved != null) { type = resolved; } // if unable to fully resolve the type variable, use its bounds, e.g. T extends Number if (type instanceof TypeVariable<?>) { type = ((TypeVariable<?>) type).getBounds()[0]; } // loop in case T extends U and U is also a type variable } return type; } }