package com.fasterxml.jackson.databind.type; import java.lang.reflect.*; import java.util.*; import com.fasterxml.jackson.databind.JavaType; /** * 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; /** * 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, Class<?> cc) { this(typeFactory, null, cc, null); } public TypeBindings(TypeFactory typeFactory, JavaType type) { this(typeFactory, null, 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, _contextClass, _contextType); } private TypeBindings(TypeFactory tf, TypeBindings parent, Class<?> cc, JavaType type) { _typeFactory = tf; _parentBindings = parent; _contextClass = cc; _contextType = type; } /* /********************************************************** /* Pass-through type resolution methods /********************************************************** */ public JavaType resolveType(Class<?> cls) { return _typeFactory._constructType(cls, this); } public JavaType resolveType(Type type) { return _typeFactory._constructType(type, this); } /* /********************************************************** /* Accesors /********************************************************** */ public int getBindingCount() { if (_bindings == null) { _resolve(); } return _bindings.size(); } public JavaType findType(String name) { 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); } // nothing found, so... // Should we throw an exception or just return null? /* [JACKSON-499] 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) { Class<?> enclosing = _contextClass.getEnclosingClass(); if (enclosing != 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; } // ... so this piece of code should not be needed any more /* Package pkg = enclosing.getPackage(); if (pkg != null) { // as per [JACKSON-533], also include "java.util.concurrent": if (pkg.getName().startsWith("java.util")) { return UNBOUND; } } */ } } 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 /********************************************************** */ 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(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 */ _resolveBindings(raw.getDeclaringClass()); /* 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) { _bindings.put(name, typeParams[i]); } else { _bindings.put(name, _typeFactory._constructType(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(raw.getGenericSuperclass()); 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(); } }