/*
* #%L
* FlatPack serialization code
* %%
* Copyright (C) 2012 Perka 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.
* #L%
*/
package com.getperka.flatpack.util;
import static com.getperka.flatpack.util.FlatPackCollections.mapForLookup;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
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.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
/**
* Miscellaneous typesystem-querying methods.
*/
public class FlatPackTypes {
public static final List<Class<?>> BOXED_TYPES = Collections.unmodifiableList(
Arrays.<Class<?>> asList(Boolean.class, Byte.class, Character.class, Double.class,
Float.class, Integer.class, Long.class, Short.class, Void.class));
public static final List<Class<?>> PRIMITIVE_TYPES = Collections.unmodifiableList(
Arrays.<Class<?>> asList(boolean.class, byte.class, char.class, double.class, float.class,
int.class, long.class, short.class, void.class));
public static final Charset UTF8 = Charset.forName("UTF8");
private static final Map<Class<?>, Object> DEFAULT_VALUES = mapForLookup();
static {
DEFAULT_VALUES.put(boolean.class, false);
DEFAULT_VALUES.put(byte.class, (byte) 0);
DEFAULT_VALUES.put(char.class, (char) 0);
DEFAULT_VALUES.put(double.class, (double) 0);
DEFAULT_VALUES.put(float.class, (float) 0);
DEFAULT_VALUES.put(int.class, 0);
DEFAULT_VALUES.put(long.class, (long) 0);
DEFAULT_VALUES.put(short.class, (short) 0);
DEFAULT_VALUES.put(void.class, null);
}
public static Class<?> box(Class<?> primitive) {
assert PRIMITIVE_TYPES.contains(primitive);
return BOXED_TYPES.get(PRIMITIVE_TYPES.indexOf(primitive));
}
/**
* Construct as possible parameterized type from a flattened representation of a generic
* parameterization.
*/
public static Type createType(Class<?>... flatParameterization) {
return createType(Arrays.<Type> asList(flatParameterization));
}
/**
* Construct as possible parameterized type from a flattened representation of a generic
* parameterization.
*/
public static Type createType(List<? extends Type> flatParameterization) {
return createType(flatParameterization.iterator());
}
/**
* Construct as possible parameterized type from a flattened representation of a generic
* parameterization.
*/
public static Type createType(Type... flatParameterization) {
return createType(Arrays.<Type> asList(flatParameterization));
}
/**
* Portable implementation of decapitalize.
*/
public static String decapitalize(String string) {
if (string == null || string.isEmpty()) {
return string;
} else if (string.length() == 1) {
return String.valueOf(Character.toLowerCase(string.charAt(0)));
} else {
return Character.toLowerCase(string.charAt(0)) + string.substring(1);
}
}
/**
* Determine the erasure of a given type.
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> erase(Type type) {
if (type instanceof Class) {
return (Class<T>) type;
}
if (type instanceof GenericArrayType) {
Class<?> component = erase(((GenericArrayType) type).getGenericComponentType());
return (Class<T>) Array.newInstance(component, 0).getClass();
}
if (type instanceof ParameterizedType) {
return erase(((ParameterizedType) type).getRawType());
}
if (type instanceof TypeVariable) {
return erase(((TypeVariable<?>) type).getBounds()[0]);
}
if (type instanceof WildcardType) {
return erase(((WildcardType) type).getUpperBounds()[0]);
}
throw new RuntimeException("Unhandled type " + type.getClass().getName());
}
/**
* Recursively flatten a ParameterizedType into a list of simple types.
*/
public static List<Type> flatten(Type type) {
List<Type> toReturn = FlatPackCollections.listForAny();
if (type instanceof ParameterizedType) {
ParameterizedType param = (ParameterizedType) type;
toReturn.add(erase(param.getRawType()));
for (Type arg : param.getActualTypeArguments()) {
toReturn.addAll(flatten(arg));
}
} else {
toReturn.add(type);
}
return toReturn;
}
/**
* Returns the default, uninitialized value for a given type, including primitives.
*/
public static Object getDefaultValue(Class<?> clazz) {
return DEFAULT_VALUES.get(clazz);
}
/**
* Given a type {@code Foo<Bar, Baz>} or a type derived therefrom and search class {@code Foo},
* return {@code Bar, Baz}.
*/
public static Type[] getParameterization(Class<?> search, Type... types) {
for (Type type : types) {
if (type == null) {
continue;
} else if (type instanceof Class<?>) {
/*
* Classes are straightforward. If there's a FooList extends ArrayList<Foo> (thus
* implementing List<Foo>), we trawl the super-interface and then super-class hierarchies.
*/
Class<?> clazz = (Class<?>) type;
if (search.equals(clazz)) {
return search.getTypeParameters();
}
Type[] found = getParameterization(search, clazz.getGenericSuperclass());
if (found != null) {
return found;
}
found = getParameterization(search, clazz.getGenericInterfaces());
if (found != null) {
return found;
}
} else if (type instanceof ParameterizedType) {
/*
* First, build a map of the raw type's type parameters to types used in the
* parameterization. For example, List<Foo> has a raw type of List<T> and the map has T ->
* Foo.
*/
ParameterizedType param = (ParameterizedType) type;
Type[] actualTypeArguments = param.getActualTypeArguments();
Class<?> erased = erase(param.getRawType());
Type[] typeParameters = erased.getTypeParameters();
Map<Type, Type> map = FlatPackCollections.mapForLookup();
for (int i = 0, j = typeParameters.length; i < j; i++) {
map.put(typeParameters[i], actualTypeArguments[i]);
}
/*
* If we've descended into the type hierarchy enough to find the type we're looking for, say
* Collection, we'll use its type parameters to pull the relevant data from the previous
* map. Otherwise, ascend into the super-interfaces and extract the relevant type parameters
* (e.g. List<T> extends Collection<T> so we can find the T from Collection).
*/
Type[] lookFor = search.equals(erased) ? search.getTypeParameters()
: getParameterization(search, erased.getGenericInterfaces());
if (lookFor == null) {
lookFor = getParameterization(search, erased.getGenericSuperclass());
if (lookFor == null) {
continue;
}
}
// /*
// * Now that we have the type parameters to look for, pull them out of the collected map.
// */
// List<Type> toReturn = FlatPackCollections.listForAny();
// for (int i = 0, j = lookFor.length; i < j; i++) {
// Type found = map.get(lookFor[i]);
// if (found != null) {
// toReturn.add(found);
// }
// }
List<Type> toReturn = FlatPackCollections.listForAny();
for (int i = 0, j = lookFor.length; i < j; i++) {
toReturn.add(replaceTypes(map, lookFor[i]));
}
// Done.
return toReturn.toArray(new Type[toReturn.size()]);
}
}
return null;
}
/**
* Given a type {@code Foo<Bar, Baz>} or a type derived therefrom and search class {@code Foo},
* return {@code Bar}. If the given type is not assignable to {@code search}, {@code null} will be
* returned.
*/
public static Type getSingleParameterization(Type type, Class<?> search) {
Type[] types = getParameterization(search, type);
return types == null ? null : types[0];
}
/**
* Returns {@code true} if the given element has an annotatiod with the given simple name.
*/
public static boolean hasAnnotationWithSimpleName(AnnotatedElement elt, String simpleName) {
for (Annotation a : elt.getAnnotations()) {
if (simpleName.equals(a.annotationType().getSimpleName())) {
return true;
}
}
return false;
}
/**
* Erases {@link WildcardType} and {@link TypeVariable} instances, returning all other Types
* unmodified.
*/
public static Type instantiable(Type type) {
if (type instanceof TypeVariable || type instanceof WildcardType) {
return erase(type);
}
return type;
}
/**
* Given a class that represents a boxed primitive type, return the primitive type.
*/
public static Class<?> unbox(Class<?> boxed) {
assert BOXED_TYPES.contains(boxed);
return PRIMITIVE_TYPES.get(BOXED_TYPES.indexOf(boxed));
}
/**
* @see #createType(List)
*/
private static Type createType(Iterator<? extends Type> it) {
final Type raw = it.next();
if (!(raw instanceof Class<?>)) {
return raw;
}
TypeVariable<?>[] vars = ((Class<?>) raw).getTypeParameters();
// If the type has no parameterizations, just return it
if (vars.length == 0 || !it.hasNext()) {
return raw;
}
// Create the parameter values
final Type[] typeArguments = new Type[vars.length];
for (int i = 0, j = typeArguments.length; i < j; i++) {
typeArguments[i] = createType(it);
}
// Return a carrier object
return new ParameterizedType() {
@Override
public Type[] getActualTypeArguments() {
return typeArguments;
}
@Override
public Type getOwnerType() {
return null;
}
@Override
public Type getRawType() {
return raw;
}
};
}
private static Type replaceTypes(Map<Type, Type> replacements, Type toReplace) {
List<Type> flat = flatten(toReplace);
for (ListIterator<Type> it = flat.listIterator(); it.hasNext();) {
Type type = it.next();
if (replacements.containsKey(type)) {
it.set(replacements.get(type));
}
}
return createType(flat);
}
}