com.fasterxml.jackson.databind.type.TypeFactory Maven / Gradle / Ivy
package com.fasterxml.jackson.databind.type;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.lang.reflect.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.util.ArrayBuilders;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.LRUMap;
/**
* Class used for creating concrete {@link JavaType} instances,
* given various inputs.
*
* Instances of this class are accessible using {@link com.fasterxml.jackson.databind.ObjectMapper}
* as well as many objects it constructs (like
* {@link com.fasterxml.jackson.databind.DeserializationConfig} and
* {@link com.fasterxml.jackson.databind.SerializationConfig})),
* but usually those objects also
* expose convenience methods (constructType
).
* So, you can do for example:
*
* JavaType stringType = mapper.constructType(String.class);
*
* However, more advanced methods are only exposed by factory so that you
* may need to use:
*
* JavaType stringCollection = mapper.getTypeFactory().constructCollectionType(List.class, String.class);
*
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public final class TypeFactory
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private final static JavaType[] NO_TYPES = new JavaType[0];
/**
* Globally shared singleton. Not accessed directly; non-core
* code should use per-ObjectMapper instance (via configuration objects).
* Core Jackson code uses {@link #defaultInstance} for accessing it.
*/
protected final static TypeFactory instance = new TypeFactory();
/*
/**********************************************************
/* Caching
/**********************************************************
*/
// // // Let's assume that a small set of core primitive/basic types
// // // will not be modified, and can be freely shared to streamline
// // // parts of processing
protected final static SimpleType CORE_TYPE_STRING = new SimpleType(String.class);
protected final static SimpleType CORE_TYPE_BOOL = new SimpleType(Boolean.TYPE);
protected final static SimpleType CORE_TYPE_INT = new SimpleType(Integer.TYPE);
protected final static SimpleType CORE_TYPE_LONG = new SimpleType(Long.TYPE);
/**
* Since type resolution can be expensive (specifically when resolving
* actual generic types), we will use small cache to avoid repetitive
* resolution of core types
*/
protected final LRUMap _typeCache = new LRUMap(16, 100);
/*
* Looks like construction of {@link JavaType} instances can be
* a bottleneck, esp. for root-level Maps, so we better do bit
* of low-level component caching here...
*/
/**
* Lazily constructed copy of type hierarchy from {@link java.util.HashMap}
* to its supertypes.
*/
protected transient HierarchicType _cachedHashMapType;
/**
* Lazily constructed copy of type hierarchy from {@link java.util.ArrayList}
* to its supertypes.
*/
protected transient HierarchicType _cachedArrayListType;
/*
/**********************************************************
/* Configuration
/**********************************************************
*/
/**
* Registered {@link TypeModifier}s: objects that can change details
* of {@link JavaType} instances factory constructs.
*/
protected final TypeModifier[] _modifiers;
protected final TypeParser _parser;
/**
* ClassLoader used by this factory (Issue #624)
*/
protected final ClassLoader _classLoader;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
private TypeFactory() {
_parser = new TypeParser(this);
_modifiers = null;
_classLoader = null;
}
protected TypeFactory(TypeParser p, TypeModifier[] mods) {
this(p, mods, null);
}
protected TypeFactory(TypeParser p, TypeModifier[] mods, ClassLoader classLoader) {
// As per [databind#894] must ensure we have back-linkage from TypeFactory:
_parser = p.withFactory(this);
_modifiers = mods;
_classLoader = classLoader;
}
public TypeFactory withModifier(TypeModifier mod)
{
if (mod == null) { // mostly for unit tests
return new TypeFactory(_parser, _modifiers, _classLoader);
}
if (_modifiers == null) {
return new TypeFactory(_parser, new TypeModifier[] { mod }, _classLoader);
}
return new TypeFactory(_parser, ArrayBuilders.insertInListNoDup(_modifiers, mod), _classLoader);
}
public TypeFactory withClassLoader(ClassLoader classLoader) {
return new TypeFactory(_parser, _modifiers, classLoader);
}
/**
* Method used to access the globally shared instance, which has
* no custom configuration. Used by ObjectMapper
to
* get the default factory when constructed.
*/
public static TypeFactory defaultInstance() { return instance; }
/**
* Method that will clear up any cached type definitions that may
* be cached by this {@link TypeFactory} instance.
* This method should not be commonly used, that is, only use it
* if you know there is a problem with retention of type definitions;
* the most likely (and currently only known) problem is retention
* of {@link Class} instances via {@link JavaType} reference.
*
* @since 2.4.1
*/
public void clearCache() {
_typeCache.clear();
}
/*
* Getters
*/
public ClassLoader getClassLoader() {
return _classLoader;
}
/*
/**********************************************************
/* Static methods for non-instance-specific functionality
/**********************************************************
*/
/**
* Method for constructing a marker type that indicates missing generic
* type information, which is handled same as simple type for
* java.lang.Object
.
*/
public static JavaType unknownType() {
return defaultInstance()._unknownType();
}
/**
* Static helper method that can be called to figure out type-erased
* call for given JDK type. It can be called statically since type resolution
* process can never change actual type-erased class; thereby static
* default instance is used for determination.
*/
public static Class rawClass(Type t) {
if (t instanceof Class) {
return (Class) t;
}
// Shouldbe able to optimize bit more in future...
return defaultInstance().constructType(t).getRawClass();
}
/*
/**********************************************************
/* Low-level helper methods
/**********************************************************
*/
/**
* Low-level lookup method moved from {@link com.fasterxml.jackson.databind.util.ClassUtil},
* to allow for overriding of lookup functionality in environments like OSGi.
*
* @since 2.6
*/
public Class findClass(String className) throws ClassNotFoundException
{
if (className.indexOf('.') < 0) {
Class prim = _findPrimitive(className);
if (prim != null) {
return prim;
}
}
// Two-phase lookup: first using context ClassLoader; then default
Throwable prob = null;
ClassLoader loader = this.getClassLoader();
if (loader == null) {
loader = Thread.currentThread().getContextClassLoader();
}
if (loader != null) {
try {
return classForName(className, true, loader);
} catch (Exception e) {
prob = ClassUtil.getRootCause(e);
}
}
try {
return classForName(className);
} catch (Exception e) {
if (prob == null) {
prob = ClassUtil.getRootCause(e);
}
}
if (prob instanceof RuntimeException) {
throw (RuntimeException) prob;
}
throw new ClassNotFoundException(prob.getMessage(), prob);
}
protected Class classForName(String name, boolean initialize,
ClassLoader loader) throws ClassNotFoundException {
return Class.forName(name, true, loader);
}
protected Class classForName(String name) throws ClassNotFoundException {
return Class.forName(name);
}
protected Class _findPrimitive(String className)
{
if ("int".equals(className)) return Integer.TYPE;
if ("long".equals(className)) return Long.TYPE;
if ("float".equals(className)) return Float.TYPE;
if ("double".equals(className)) return Double.TYPE;
if ("boolean".equals(className)) return Boolean.TYPE;
if ("byte".equals(className)) return Byte.TYPE;
if ("char".equals(className)) return Character.TYPE;
if ("short".equals(className)) return Short.TYPE;
if ("void".equals(className)) return Void.TYPE;
return null;
}
/*
/**********************************************************
/* Type conversion, parameterization resolution methods
/**********************************************************
*/
/**
* Factory method for creating a subtype of given base type, as defined
* by specified subclass; but retaining generic type information if any.
* Can be used, for example, to get equivalent of "HashMap<String,Integer>"
* from "Map<String,Integer>" by giving HashMap.class
* as subclass.
*/
public JavaType constructSpecializedType(JavaType baseType, Class subclass)
{
// simple optimization to avoid costly introspection if type-erased type does NOT differ
if (baseType.getRawClass() == subclass) {
return baseType;
}
// Currently only SimpleType instances can become something else
if (baseType instanceof SimpleType) {
// and only if subclass is an array, Collection or Map
if (subclass.isArray()
|| Map.class.isAssignableFrom(subclass)
|| Collection.class.isAssignableFrom(subclass)) {
// need to assert type compatibility...
if (!baseType.getRawClass().isAssignableFrom(subclass)) {
throw new IllegalArgumentException("Class "+subclass.getClass().getName()+" not subtype of "+baseType);
}
// this _should_ work, right?
JavaType subtype = _fromClass(subclass, new TypeBindings(this, baseType.getRawClass()));
// one more thing: handlers to copy?
Object h = baseType.getValueHandler();
if (h != null) {
subtype = subtype.withValueHandler(h);
}
h = baseType.getTypeHandler();
if (h != null) {
subtype = subtype.withTypeHandler(h);
}
return subtype;
}
}
// otherwise regular narrowing should work just fine
return baseType.narrowBy(subclass);
}
/**
* Factory method for constructing a {@link JavaType} out of its canonical
* representation (see {@link JavaType#toCanonical()}).
*
* @param canonical Canonical string representation of a type
*
* @throws IllegalArgumentException If canonical representation is malformed,
* or class that type represents (including its generic parameters) is
* not found
*/
public JavaType constructFromCanonical(String canonical) throws IllegalArgumentException
{
return _parser.parse(canonical);
}
/**
* Method that is to figure out actual type parameters that given
* class binds to generic types defined by given (generic)
* interface or class.
* This could mean, for example, trying to figure out
* key and value types for Map implementations.
*
* @param type Sub-type (leaf type) that implements expType
*/
public JavaType[] findTypeParameters(JavaType type, Class expType)
{
/* Tricky part here is that some JavaType instances have been constructed
* from generic type (usually via TypeReference); and in those case
* types have been resolved. Alternative is that the leaf type is type-erased
* class, in which case this has not been done.
* For now simplest way to handle this is to split processing in two: latter
* case actually fully works; and former mostly works. In future may need to
* rewrite former part, which requires changes to JavaType as well.
*/
if (expType == type.getParameterSource()) {
// Direct type info; good since we can return it as is
int count = type.containedTypeCount();
if (count == 0) return null;
JavaType[] result = new JavaType[count];
for (int i = 0; i < count; ++i) {
result[i] = type.containedType(i);
}
return result;
}
/* Otherwise need to go through type-erased class. This may miss cases where
* we get generic type; ideally JavaType/SimpleType would retain information
* about generic declaration at main level... but let's worry about that
* if/when there are problems; current handling is an improvement over earlier
* code.
*/
Class raw = type.getRawClass();
return findTypeParameters(raw, expType, new TypeBindings(this, type));
}
/**
* @since 2.7
*/
public JavaType[] findTypeParameters(JavaType type, Class expType, TypeBindings bindings)
{
if (expType == type.getParameterSource()) {
int count = type.containedTypeCount();
if (count == 0) return null;
JavaType[] result = new JavaType[count];
for (int i = 0; i < count; ++i) {
result[i] = type.containedType(i);
}
return result;
}
Class raw = type.getRawClass();
return findTypeParameters(raw, expType, bindings);
}
public JavaType[] findTypeParameters(Class clz, Class expType) {
return findTypeParameters(clz, expType, new TypeBindings(this, clz));
}
public JavaType[] findTypeParameters(Class clz, Class expType, TypeBindings bindings)
{
// First: find full inheritance chain
HierarchicType subType = _findSuperTypeChain(clz, expType);
// Caller is supposed to ensure this never happens, so:
if (subType == null) {
throw new IllegalArgumentException("Class "+clz.getName()+" is not a subtype of "+expType.getName());
}
// Ok and then go to the ultimate super-type:
HierarchicType superType = subType;
while (superType.getSuperType() != null) {
superType = superType.getSuperType();
Class raw = superType.getRawClass();
TypeBindings newBindings = new TypeBindings(this, raw);
if (superType.isGeneric()) { // got bindings, need to resolve
ParameterizedType pt = superType.asGeneric();
Type[] actualTypes = pt.getActualTypeArguments();
TypeVariable[] vars = raw.getTypeParameters();
int len = actualTypes.length;
for (int i = 0; i < len; ++i) {
String name = vars[i].getName();
JavaType type = _constructType(actualTypes[i], bindings);
newBindings.addBinding(name, type);
}
}
bindings = newBindings;
}
// which ought to be generic (if not, it's raw type)
if (!superType.isGeneric()) {
return null;
}
return bindings.typesAsArray();
}
/**
* Method that can be called to figure out more specific of two
* types (if they are related; that is, one implements or extends the
* other); or if not related, return the primary type.
*
* @param type1 Primary type to consider
* @param type2 Secondary type to consider
*
* @since 2.2
*/
public JavaType moreSpecificType(JavaType type1, JavaType type2)
{
if (type1 == null) {
return type2;
}
if (type2 == null) {
return type1;
}
Class raw1 = type1.getRawClass();
Class raw2 = type2.getRawClass();
if (raw1 == raw2) {
return type1;
}
// TODO: maybe try sub-classing, to retain generic types?
if (raw1.isAssignableFrom(raw2)) {
return type2;
}
return type1;
}
/*
/**********************************************************
/* Public factory methods
/**********************************************************
*/
public JavaType constructType(Type type) {
return _constructType(type, null);
}
public JavaType constructType(Type type, TypeBindings bindings) {
return _constructType(type, bindings);
}
public JavaType constructType(TypeReference typeRef) {
return _constructType(typeRef.getType(), null);
}
public JavaType constructType(Type type, Class context) {
TypeBindings b = (context == null) ? null : new TypeBindings(this, context);
return _constructType(type, b);
}
public JavaType constructType(Type type, JavaType context) {
TypeBindings b = (context == null) ? null : new TypeBindings(this, context);
return _constructType(type, b);
}
/**
* Factory method that can be used if type information is passed
* as Java typing returned from getGenericXxx
methods
* (usually for a return or argument type).
*/
protected JavaType _constructType(Type type, TypeBindings context)
{
JavaType resultType;
// simple class?
if (type instanceof Class) {
resultType = _fromClass((Class) type, context);
}
// But if not, need to start resolving.
else if (type instanceof ParameterizedType) {
resultType = _fromParamType((ParameterizedType) type, context);
}
else if (type instanceof JavaType) { // [Issue#116]
return (JavaType) type;
}
else if (type instanceof GenericArrayType) {
resultType = _fromArrayType((GenericArrayType) type, context);
}
else if (type instanceof TypeVariable) {
resultType = _fromVariable((TypeVariable) type, context);
}
else if (type instanceof WildcardType) {
resultType = _fromWildcard((WildcardType) type, context);
} else {
// sanity check
throw new IllegalArgumentException("Unrecognized Type: "+((type == null) ? "[null]" : type.toString()));
}
/* [JACKSON-521]: Need to allow TypeModifiers to alter actual type; however,
* for now only call for simple types (i.e. not for arrays, map or collections).
* Can be changed in future it necessary
*/
if (_modifiers != null && !resultType.isContainerType()) {
for (TypeModifier mod : _modifiers) {
resultType = mod.modifyType(resultType, type, context, this);
}
}
return resultType;
}
/*
/**********************************************************
/* Direct factory methods
/**********************************************************
*/
/**
* Method for constructing an {@link ArrayType}.
*
* NOTE: type modifiers are NOT called on array type itself; but are called
* for element type (and other contained types)
*/
public ArrayType constructArrayType(Class elementType) {
return ArrayType.construct(_constructType(elementType, null), null, null);
}
/**
* Method for constructing an {@link ArrayType}.
*
* NOTE: type modifiers are NOT called on array type itself; but are called
* for contained types.
*/
public ArrayType constructArrayType(JavaType elementType) {
return ArrayType.construct(elementType, null, null);
}
/**
* Method for constructing a {@link CollectionType}.
*
* NOTE: type modifiers are NOT called on Collection type itself; but are called
* for contained types.
*/
public CollectionType constructCollectionType(Class collectionClass, Class elementClass) {
return CollectionType.construct(collectionClass, constructType(elementClass));
}
/**
* Method for constructing a {@link CollectionType}.
*
* NOTE: type modifiers are NOT called on Collection type itself; but are called
* for contained types.
*/
public CollectionType constructCollectionType(Class collectionClass, JavaType elementType) {
return CollectionType.construct(collectionClass, elementType);
}
/**
* Method for constructing a {@link CollectionLikeType}.
*
* NOTE: type modifiers are NOT called on constructed type itself; but are called
* for contained types.
*/
public CollectionLikeType constructCollectionLikeType(Class collectionClass, Class elementClass) {
return CollectionLikeType.construct(collectionClass, constructType(elementClass));
}
/**
* Method for constructing a {@link CollectionLikeType}.
*
* NOTE: type modifiers are NOT called on constructed type itself; but are called
* for contained types.
*/
public CollectionLikeType constructCollectionLikeType(Class collectionClass, JavaType elementType) {
return CollectionLikeType.construct(collectionClass, elementType);
}
/**
* Method for constructing a {@link MapType} instance
*
* NOTE: type modifiers are NOT called on constructed type itself; but are called
* for contained types.
*/
public MapType constructMapType(Class mapClass, JavaType keyType, JavaType valueType) {
return MapType.construct(mapClass, keyType, valueType);
}
/**
* Method for constructing a {@link MapType} instance
*
* NOTE: type modifiers are NOT called on constructed type itself; but are called
* for contained types.
*/
public MapType constructMapType(Class mapClass, Class keyClass, Class valueClass) {
return MapType.construct(mapClass, constructType(keyClass), constructType(valueClass));
}
/**
* Method for constructing a {@link MapLikeType} instance
*
* NOTE: type modifiers are NOT called on constructed type itself; but are called
* for contained types.
*/
public MapLikeType constructMapLikeType(Class mapClass, JavaType keyType, JavaType valueType) {
return MapLikeType.construct(mapClass, keyType, valueType);
}
/**
* Method for constructing a {@link MapLikeType} instance
*
* NOTE: type modifiers are NOT called on constructed type itself; but are called
* for contained types.
*/
public MapLikeType constructMapLikeType(Class mapClass, Class keyClass, Class valueClass) {
return MapType.construct(mapClass, constructType(keyClass), constructType(valueClass));
}
/**
* Method for constructing a type instance with specified parameterization.
*
* @deprecated Since 2.5, use variant that takes one more argument
*/
@Deprecated
public JavaType constructSimpleType(Class rawType, JavaType[] parameterTypes) {
return constructSimpleType(rawType, rawType, parameterTypes);
}
/**
* Method for constructing a type instance with specified parameterization.
*/
public JavaType constructSimpleType(Class rawType, Class parameterTarget,
JavaType[] parameterTypes)
{
// Quick sanity check: must match numbers of types with expected...
TypeVariable[] typeVars = parameterTarget.getTypeParameters();
if (typeVars.length != parameterTypes.length) {
throw new IllegalArgumentException("Parameter type mismatch for "+rawType.getName()
+" (and target "+parameterTarget.getName()+"): expected "+typeVars.length
+" parameters, was given "+parameterTypes.length);
}
String[] names = new String[typeVars.length];
for (int i = 0, len = typeVars.length; i < len; ++i) {
names[i] = typeVars[i].getName();
}
return new SimpleType(rawType, names, parameterTypes, null, null, false, parameterTarget);
}
/**
* @since 2.6
*/
public JavaType constructReferenceType(Class rawType, JavaType refType)
{
return new ReferenceType(rawType, refType, null, null, false);
}
/**
* Method that will force construction of a simple type, without trying to
* check for more specialized types.
*
* NOTE: no type modifiers are called on type either, so calling this method
* should only be used if caller really knows what it's doing...
*/
public JavaType uncheckedSimpleType(Class cls) {
return new SimpleType(cls);
}
/**
* Factory method for constructing {@link JavaType} that
* represents a parameterized type. For example, to represent
* type List<Integer>
, you could
* call
*
* TypeFactory.constructParametrizedType(List.class, List.class, Integer.class);
*
*
* The reason for first two arguments to be separate is that parameterization may
* apply to a super-type. For example, if generic type was instead to be
* constructed for ArrayList<Integer>
, the usual call would be:
*
* TypeFactory.constructParametrizedType(ArrayList.class, List.class, Integer.class);
*
* since parameterization is applied to {@link java.util.List}.
* In most cases distinction does not matter, but there are types where it does;
* one such example is parameterization of types that implement {@link java.util.Iterator}.
*
* NOTE: type modifiers are NOT called on constructed type itself; but are called
* when resolving parameterClasses
into {@link JavaType}.
*
* @param parametrized Type-erased type of instance being constructed
* @param parametersFor class or interface for which type parameters are applied; either
* parametrized
or one of its supertypes
* @param parameterClasses Type parameters to apply
*
* @since 2.5
*/
public JavaType constructParametrizedType(Class parametrized, Class parametersFor,
Class... parameterClasses)
{
int len = parameterClasses.length;
JavaType[] pt = new JavaType[len];
for (int i = 0; i < len; ++i) {
pt[i] = _fromClass(parameterClasses[i], null);
}
return constructParametrizedType(parametrized, parametersFor, pt);
}
/**
* @deprecated Since 2.5, use {@link #constructParametrizedType} instead.
*/
@Deprecated
public JavaType constructParametricType(Class parametrized, Class... parameterClasses) {
return constructParametrizedType(parametrized, parametrized, parameterClasses);
}
/**
* Factory method for constructing {@link JavaType} that
* represents a parameterized type. For example, to represent
* type List<Set<Integer>>
, you could
* call
*
* JavaType inner = TypeFactory.constructParametrizedType(Set.class, Set.class, Integer.class);
* return TypeFactory.constructParametrizedType(ArrayList.class, List.class, inner);
*
*
* The reason for first two arguments to be separate is that parameterization may
* apply to a super-type. For example, if generic type was instead to be
* constructed for ArrayList<Integer>
, the usual call would be:
*
* TypeFactory.constructParametrizedType(ArrayList.class, List.class, Integer.class);
*
* since parameterization is applied to {@link java.util.List}.
* In most cases distinction does not matter, but there are types where it does;
* one such example is parameterization of types that implement {@link java.util.Iterator}.
*
* NOTE: type modifiers are NOT called on constructed type.
*
* @param parametrized Actual full type
* @param parametersFor class or interface for which type parameters are applied; either
* parametrized
or one of its supertypes
* @param parameterTypes Type parameters to apply
*
* @since 2.5
*/
public JavaType constructParametrizedType(Class parametrized, Class parametersFor,
JavaType... parameterTypes)
{
JavaType resultType;
// Need to check kind of class we are dealing with...
if (parametrized.isArray()) {
// 19-Jan-2010, tatus: should we support multi-dimensional arrays directly?
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("Need exactly 1 parameter type for arrays ("+parametrized.getName()+")");
}
resultType = constructArrayType(parameterTypes[0]);
}
else if (Map.class.isAssignableFrom(parametrized)) {
if (parameterTypes.length != 2) {
throw new IllegalArgumentException("Need exactly 2 parameter types for Map types ("+parametrized.getName()+")");
}
resultType = constructMapType((Class