1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
|
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();
}
}
|