com.rt.storage.api.client.util.Data Maven / Gradle / Ivy
package com.rt.storage.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.Arrays;
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, Object> NULL_CACHE =
new ConcurrentHashMap, 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 nullOf(Class objClass) {
// ConcurrentMap.computeIfAbsent is explicitly NOT used in the following logic. The
// ConcurrentHashMap implementation of that method BLOCKS if the mappingFunction triggers
// modification of the map which createNullInstance can do depending on the state of class
// loading.
Object result = NULL_CACHE.get(objClass);
if (result == null) {
// If nullOf is called concurrently for the same class createNullInstance may be executed
// multiple times. However putIfAbsent ensures that no matter what the concurrent access
// pattern looks like callers always get a singleton instance returned. Since
// createNullInstance has no side-effects beyond triggering class loading this multiple-call
// pattern is safe.
Object newValue = createNullInstance(objClass);
result = NULL_CACHE.putIfAbsent(objClass, newValue);
if (result == null) {
result = newValue;
}
}
@SuppressWarnings("unchecked")
T tResult = (T) result;
return tResult;
}
private static Object createNullInstance(Class> objClass) {
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());
return Array.newInstance(componentType, new int[dims]);
}
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.enumValue();
return e;
}
// other classes are simpler
return Types.newInstance(objClass);
}
/**
* 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.
*
* 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.
*
* @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 mapOf(Object data) {
if (data == null || isNull(data)) {
return Collections.emptyMap();
}
if (data instanceof Map, ?>) {
@SuppressWarnings("unchecked")
Map result = (Map) data;
return result;
}
Map 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.
*
* This should not be used directly in the implementation of {@code Object.clone()}. Instead
* use {@link #deepCopy(Object, Object)} for that purpose.
*
*
Final fields cannot be changed and therefore their value won't be copied.
*
* @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 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 if ("java.util.Arrays$ArrayList".equals(dataClass.getName())) {
// Arrays$ArrayList does not have a zero-arg constructor, so it has to handled specially.
// Arrays.asList(x).toArray() may or may not have the same runtime type as x.
// https://bugs.openjdk.java.net/browse/JDK-6260652
Object[] arrayCopy = ((List>) data).toArray();
deepCopy(arrayCopy, arrayCopy);
copy = (T) Arrays.asList(arrayCopy);
return copy;
} 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()}.
*
* Example usage of this method in {@code Object.clone()}:
*
*
* @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);
* }
* }
*
*
* Final fields cannot be changed and therefore their value won't be copied.
*
* @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