blob: 7570cc2bd09a812d8da5697ecfa588d2e333adb2 [file] [log] [blame]
package com.fasterxml.jackson.databind.type;
import java.lang.reflect.*;
import java.util.*;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Helper class used for resolving type parameters for given class
*/
public class TypeBindings
{
private final static JavaType[] NO_TYPES = new JavaType[0];
/**
* Marker to use for (temporarily) unbound references.
*/
public final static JavaType UNBOUND = new SimpleType(Object.class);
/**
* Factory to use for constructing resolved related types.
*/
protected final TypeFactory _typeFactory;
/**
* @since 2.7
*/
protected final ClassStack _classStack;
/**
* Context type used for resolving all types, if specified. May be null,
* in which case {@link #_contextClass} is used instead.
*/
protected final JavaType _contextType;
/**
* Specific class to use for resolving all types, for methods and fields
* class and its superclasses and -interfaces contain.
*/
protected final Class<?> _contextClass;
/**
* Lazily-instantiated bindings of resolved type parameters
*/
protected Map<String,JavaType> _bindings;
/**
* Also: we may temporarily want to mark certain named types
* as resolved (but without exact type); if so, we'll just store
* names here.
*/
protected HashSet<String> _placeholders;
/**
* Sometimes it is necessary to allow hierarchic resolution of types: specifically
* in cases where there are local bindings (for methods, constructors). If so,
* we'll just use simple delegation model.
*/
private final TypeBindings _parentBindings;
/*
/**********************************************************
/* Construction
/**********************************************************
*/
public TypeBindings(TypeFactory typeFactory, ClassStack stack, Class<?> cc)
{
this(typeFactory, null, stack, cc, null);
}
public TypeBindings(TypeFactory typeFactory, ClassStack stack, JavaType type)
{
this(typeFactory, null, stack, type.getRawClass(), type);
}
/**
* Constructor used to create "child" instances; mostly to
* allow delegation from explicitly defined local overrides
* (local type variables for methods, constructors) to
* contextual (class-defined) ones.
*/
public TypeBindings childInstance() {
return new TypeBindings(_typeFactory, this, _classStack, _contextClass, _contextType);
}
private TypeBindings(TypeFactory tf, TypeBindings parent, ClassStack stack,
Class<?> cc, JavaType type)
{
_typeFactory = tf;
_parentBindings = parent;
_classStack = stack;
_contextClass = cc;
_contextType = type;
}
/*
/**********************************************************
/* Pass-through type resolution methods
/**********************************************************
*/
public JavaType resolveType(Class<?> cls) {
return _typeFactory._constructType(_classStack, cls, this);
}
public JavaType resolveType(Type type) {
return _typeFactory._constructType(_classStack, type, this);
}
/*
/**********************************************************
/* Accesors
/**********************************************************
*/
public JavaType findType(String name, boolean mustFind)
{
if (_bindings == null) {
_resolve();
}
JavaType t = _bindings.get(name);
if (t != null) {
return t;
}
if (_placeholders != null && _placeholders.contains(name)) {
return UNBOUND;
}
if (_parentBindings != null) {
return _parentBindings.findType(name, mustFind);
}
// nothing found, so...
// Should we throw an exception or just return null?
/* 18-Feb-2011, tatu: There are some tricky type bindings within
* java.util, such as HashMap$KeySet; so let's punt the problem
* (honestly not sure what to do -- they are unbound for good, I think)
*/
if (_contextClass != null) {
if (ClassUtil.getEnclosingClass(_contextClass) != null) {
// [JACKSON-572]: Actually, let's skip this for all non-static inner classes
// (which will also cover 'java.util' type cases...
if (!Modifier.isStatic(_contextClass.getModifiers())) {
return UNBOUND;
}
}
}
if (!mustFind) {
return null;
}
String className;
if (_contextClass != null) {
className = _contextClass.getName();
} else if (_contextType != null) {
className = _contextType.toString();
} else {
className = "UNKNOWN";
}
throw new IllegalArgumentException("Type variable '"+name
+"' can not be resolved (with context of class "+className+")");
//t = UNBOUND;
}
public void addBinding(String name, JavaType type)
{
// note: emptyMap() is unmodifiable, hence second check is needed:
if (_bindings == null || _bindings.size() == 0) {
_bindings = new LinkedHashMap<String,JavaType>();
}
_bindings.put(name, type);
}
public JavaType[] typesAsArray()
{
if (_bindings == null) {
_resolve();
}
if (_bindings.size() == 0) {
return NO_TYPES;
}
return _bindings.values().toArray(new JavaType[_bindings.size()]);
}
/*
/**********************************************************
/* Internal methods
/**********************************************************
*/
// Only for tests!
protected int getBindingCount() {
if (_bindings == null) {
_resolve();
}
return _bindings.size();
}
protected void _resolve()
{
_resolveBindings(_contextClass);
// finally: may have root level type info too
if (_contextType != null) {
int count = _contextType.containedTypeCount();
if (count > 0) {
for (int i = 0; i < count; ++i) {
String name = _contextType.containedTypeName(i);
JavaType type = _contextType.containedType(i);
addBinding(name, type);
}
}
}
// nothing bound? mark with empty map to prevent further calls
if (_bindings == null) {
_bindings = Collections.emptyMap();
}
}
public void _addPlaceholder(String name) {
if (_placeholders == null) {
_placeholders = new HashSet<String>();
}
_placeholders.add(name);
}
protected void _resolveBindings(Type t)
{
if (t == null) return;
Class<?> raw;
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
Type[] args = pt.getActualTypeArguments();
if (args != null && args.length > 0) {
Class<?> rawType = (Class<?>) pt.getRawType();
TypeVariable<?>[] vars = rawType.getTypeParameters();
if (vars.length != args.length) {
throw new IllegalArgumentException("Strange parametrized type (in class "+rawType.getName()+"): number of type arguments != number of type parameters ("+args.length+" vs "+vars.length+")");
}
for (int i = 0, len = args.length; i < len; ++i) {
TypeVariable<?> var = vars[i];
String name = var.getName();
if (_bindings == null) {
_bindings = new LinkedHashMap<String,JavaType>();
} else {
// 24-Mar-2010, tatu: Better ensure that we do not overwrite something
// collected earlier (since we descend towards super-classes):
if (_bindings.containsKey(name)) continue;
}
// first: add a placeholder to prevent infinite loops
_addPlaceholder(name);
// then resolve type
_bindings.put(name, _typeFactory._constructType(_classStack, args[i], this));
}
}
raw = (Class<?>)pt.getRawType();
} else if (t instanceof Class<?>) {
raw = (Class<?>) t;
/* [JACKSON-677]: If this is an inner class then the generics are defined on the
* enclosing class so we have to check there as well. We don't
* need to call getEnclosingClass since anonymous classes declare
* generics
*/
Class<?> decl = ClassUtil.getDeclaringClass(raw);
/* 08-Feb-2013, tatu: Except that if context is also super-class, we must
* skip it; context will be checked anyway, and we'd get StackOverflow if
* we went there.
*/
if (decl != null && !decl.isAssignableFrom(raw)) {
_resolveBindings(decl);
}
/* 24-Mar-2010, tatu: Can not have true generics definitions, but can
* have lower bounds ("<T extends BeanBase>") in declaration itself
*/
TypeVariable<?>[] vars = raw.getTypeParameters();
if (vars != null && vars.length > 0) {
JavaType[] typeParams = null;
if (_contextType != null && raw.isAssignableFrom(_contextType.getRawClass())) {
typeParams = _typeFactory.findTypeParameters(_contextType, raw);
}
for (int i = 0; i < vars.length; i++) {
TypeVariable<?> var = vars[i];
String name = var.getName();
Type varType = var.getBounds()[0];
if (varType != null) {
if (_bindings == null) {
_bindings = new LinkedHashMap<String,JavaType>();
} else { // and no overwriting...
if (_bindings.containsKey(name)) continue;
}
_addPlaceholder(name); // to prevent infinite loops
if (typeParams != null && typeParams.length > i) {
_bindings.put(name, typeParams[i]);
} else {
_bindings.put(name, _typeFactory._constructType(_classStack, varType, this));
}
}
}
}
} else { // probably can't be any of these... so let's skip for now
//if (type instanceof GenericArrayType) {
//if (type instanceof TypeVariable<?>) {
// if (type instanceof WildcardType) {
return;
}
// but even if it's not a parameterized type, its super types may be:
_resolveBindings(ClassUtil.getGenericSuperclass(raw));
for (Type intType : raw.getGenericInterfaces()) {
_resolveBindings(intType);
}
}
@Override
public String toString()
{
if (_bindings == null) {
_resolve();
}
StringBuilder sb = new StringBuilder("[TypeBindings for ");
if (_contextType != null) {
sb.append(_contextType.toString());
} else {
sb.append(_contextClass.getName());
}
sb.append(": ").append(_bindings).append("]");
return sb.toString();
}
}