1 package com.fasterxml.jackson.databind.type; 2 3 import java.lang.reflect.*; 4 import java.util.*; 5 6 import com.fasterxml.jackson.databind.JavaType; 7 import com.fasterxml.jackson.databind.util.ClassUtil; 8 9 /** 10 * Helper class used for resolving type parameters for given class 11 */ 12 public class TypeBindings 13 implements java.io.Serializable 14 { 15 private static final long serialVersionUID = 1L; 16 17 private final static String[] NO_STRINGS = new String[0]; 18 19 private final static JavaType[] NO_TYPES = new JavaType[0]; 20 21 private final static TypeBindings EMPTY = new TypeBindings(NO_STRINGS, NO_TYPES, null); 22 23 // // // Pre-resolved instances for minor optimizations 24 25 // // // Actual member information 26 27 /** 28 * Array of type (type variable) names. 29 */ 30 private final String[] _names; 31 32 /** 33 * Types matching names 34 */ 35 private final JavaType[] _types; 36 37 /** 38 * Names of potentially unresolved type variables. 39 * 40 * @since 2.3 41 */ 42 private final String[] _unboundVariables; 43 44 private final int _hashCode; 45 46 /* 47 /********************************************************************** 48 /* Construction 49 /********************************************************************** 50 */ 51 TypeBindings(String[] names, JavaType[] types, String[] uvars)52 private TypeBindings(String[] names, JavaType[] types, String[] uvars) 53 { 54 _names = (names == null) ? NO_STRINGS : names; 55 _types = (types == null) ? NO_TYPES : types; 56 if (_names.length != _types.length) { 57 throw new IllegalArgumentException("Mismatching names ("+_names.length+"), types ("+_types.length+")"); 58 } 59 int h = 1; 60 for (int i = 0, len = _types.length; i < len; ++i) { 61 h += _types[i].hashCode(); 62 } 63 _unboundVariables = uvars; 64 _hashCode = h; 65 } 66 emptyBindings()67 public static TypeBindings emptyBindings() { 68 return EMPTY; 69 } 70 71 // Let's just canonicalize serialized EMPTY back to static instance, if need be readResolve()72 protected Object readResolve() { 73 if ((_names == null) || (_names.length == 0)) { 74 return EMPTY; 75 } 76 return this; 77 } 78 79 /** 80 * Factory method for constructing bindings for given class using specified type 81 * parameters. 82 */ create(Class<?> erasedType, List<JavaType> typeList)83 public static TypeBindings create(Class<?> erasedType, List<JavaType> typeList) 84 { 85 JavaType[] types = (typeList == null || typeList.isEmpty()) ? 86 NO_TYPES : typeList.toArray(NO_TYPES); 87 return create(erasedType, types); 88 } 89 create(Class<?> erasedType, JavaType[] types)90 public static TypeBindings create(Class<?> erasedType, JavaType[] types) 91 { 92 if (types == null) { 93 types = NO_TYPES; 94 } else switch (types.length) { 95 case 1: 96 return create(erasedType, types[0]); 97 case 2: 98 return create(erasedType, types[0], types[1]); 99 } 100 TypeVariable<?>[] vars = erasedType.getTypeParameters(); 101 String[] names; 102 if (vars == null || vars.length == 0) { 103 names = NO_STRINGS; 104 } else { 105 int len = vars.length; 106 names = new String[len]; 107 for (int i = 0; i < len; ++i) { 108 names[i] = vars[i].getName(); 109 } 110 } 111 // Check here to give better error message 112 if (names.length != types.length) { 113 throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName() 114 +" with "+types.length+" type parameter" 115 +((types.length == 1) ? "" : "s")+": class expects "+names.length); 116 } 117 return new TypeBindings(names, types, null); 118 } 119 create(Class<?> erasedType, JavaType typeArg1)120 public static TypeBindings create(Class<?> erasedType, JavaType typeArg1) 121 { 122 // 30-Oct-2015, tatu: Minor optimization for relatively common cases 123 TypeVariable<?>[] vars = TypeParamStash.paramsFor1(erasedType); 124 int varLen = (vars == null) ? 0 : vars.length; 125 if (varLen != 1) { 126 throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName() 127 +" with 1 type parameter: class expects "+varLen); 128 } 129 return new TypeBindings(new String[] { vars[0].getName() }, 130 new JavaType[] { typeArg1 }, null); 131 } 132 create(Class<?> erasedType, JavaType typeArg1, JavaType typeArg2)133 public static TypeBindings create(Class<?> erasedType, JavaType typeArg1, JavaType typeArg2) 134 { 135 // 30-Oct-2015, tatu: Minor optimization for relatively common cases 136 TypeVariable<?>[] vars = TypeParamStash.paramsFor2(erasedType); 137 int varLen = (vars == null) ? 0 : vars.length; 138 if (varLen != 2) { 139 throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName() 140 +" with 2 type parameters: class expects "+varLen); 141 } 142 return new TypeBindings(new String[] { vars[0].getName(), vars[1].getName() }, 143 new JavaType[] { typeArg1, typeArg2 }, null); 144 } 145 146 /** 147 * Alternate factory method that may be called if it is possible that type 148 * does or does not require type parameters; this is mostly useful for 149 * collection- and map-like types. 150 */ createIfNeeded(Class<?> erasedType, JavaType typeArg1)151 public static TypeBindings createIfNeeded(Class<?> erasedType, JavaType typeArg1) 152 { 153 TypeVariable<?>[] vars = erasedType.getTypeParameters(); 154 int varLen = (vars == null) ? 0 : vars.length; 155 if (varLen == 0) { 156 return EMPTY; 157 } 158 if (varLen != 1) { 159 throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName() 160 +" with 1 type parameter: class expects "+varLen); 161 } 162 return new TypeBindings(new String[] { vars[0].getName() }, 163 new JavaType[] { typeArg1 }, null); 164 } 165 166 /** 167 * Alternate factory method that may be called if it is possible that type 168 * does or does not require type parameters; this is mostly useful for 169 * collection- and map-like types. 170 */ createIfNeeded(Class<?> erasedType, JavaType[] types)171 public static TypeBindings createIfNeeded(Class<?> erasedType, JavaType[] types) 172 { 173 TypeVariable<?>[] vars = erasedType.getTypeParameters(); 174 if (vars == null || vars.length == 0) { 175 return EMPTY; 176 } 177 if (types == null) { 178 types = NO_TYPES; 179 } 180 int len = vars.length; 181 String[] names = new String[len]; 182 for (int i = 0; i < len; ++i) { 183 names[i] = vars[i].getName(); 184 } 185 // Check here to give better error message 186 if (names.length != types.length) { 187 throw new IllegalArgumentException("Cannot create TypeBindings for class "+erasedType.getName() 188 +" with "+types.length+" type parameter" 189 +((types.length == 1) ? "" : "s")+": class expects "+names.length); 190 } 191 return new TypeBindings(names, types, null); 192 } 193 194 /** 195 * Method for creating an instance that has same bindings as this object, 196 * plus an indicator for additional type variable that may be unbound within 197 * this context; this is needed to resolve recursive self-references. 198 */ withUnboundVariable(String name)199 public TypeBindings withUnboundVariable(String name) 200 { 201 int len = (_unboundVariables == null) ? 0 : _unboundVariables.length; 202 String[] names = (len == 0) 203 ? new String[1] : Arrays.copyOf(_unboundVariables, len+1); 204 names[len] = name; 205 return new TypeBindings(_names, _types, names); 206 } 207 208 /* 209 /********************************************************************** 210 /* Accessors 211 /********************************************************************** 212 */ 213 214 /** 215 * Find type bound to specified name, if there is one; returns bound type if so, null if not. 216 */ findBoundType(String name)217 public JavaType findBoundType(String name) 218 { 219 for (int i = 0, len = _names.length; i < len; ++i) { 220 if (name.equals(_names[i])) { 221 JavaType t = _types[i]; 222 if (t instanceof ResolvedRecursiveType) { 223 ResolvedRecursiveType rrt = (ResolvedRecursiveType) t; 224 JavaType t2 = rrt.getSelfReferencedType(); 225 if (t2 != null) { 226 t = t2; 227 } else { 228 /* 25-Feb-2016, tatu: Looks like a potential problem, but alas 229 * we have a test where this should NOT fail and things... seem 230 * to work. So be it. 231 */ 232 /* 233 throw new IllegalStateException(String.format 234 ("Unresolved ResolvedRecursiveType for parameter '%s' (index #%d; erased type %s)", 235 name, i, t.getRawClass())); 236 */ 237 } 238 } 239 return t; 240 } 241 } 242 return null; 243 } 244 isEmpty()245 public boolean isEmpty() { 246 return (_types.length == 0); 247 } 248 249 /** 250 * Returns number of bindings contained 251 */ size()252 public int size() { 253 return _types.length; 254 } 255 getBoundName(int index)256 public String getBoundName(int index) 257 { 258 if (index < 0 || index >= _names.length) { 259 return null; 260 } 261 return _names[index]; 262 } 263 getBoundType(int index)264 public JavaType getBoundType(int index) 265 { 266 if (index < 0 || index >= _types.length) { 267 return null; 268 } 269 return _types[index]; 270 } 271 272 /** 273 * Accessor for getting bound types in declaration order 274 */ getTypeParameters()275 public List<JavaType> getTypeParameters() 276 { 277 if (_types.length == 0) { 278 return Collections.emptyList(); 279 } 280 return Arrays.asList(_types); 281 } 282 283 /** 284 * @since 2.3 285 */ hasUnbound(String name)286 public boolean hasUnbound(String name) { 287 if (_unboundVariables != null) { 288 for (int i = _unboundVariables.length; --i >= 0; ) { 289 if (name.equals(_unboundVariables[i])) { 290 return true; 291 } 292 } 293 } 294 return false; 295 } 296 297 /** 298 * Factory method that will create an object that can be used as a key for 299 * caching purposes by {@link TypeFactory} 300 * 301 * @since 2.8 302 */ asKey(Class<?> rawBase)303 public Object asKey(Class<?> rawBase) { 304 // safe to pass _types array without copy since it is not exposed via 305 // any access, nor modified by this class 306 return new AsKey(rawBase, _types, _hashCode); 307 } 308 309 /* 310 /********************************************************************** 311 /* Standard methods 312 /********************************************************************** 313 */ 314 toString()315 @Override public String toString() 316 { 317 if (_types.length == 0) { 318 return "<>"; 319 } 320 StringBuilder sb = new StringBuilder(); 321 sb.append('<'); 322 for (int i = 0, len = _types.length; i < len; ++i) { 323 if (i > 0) { 324 sb.append(','); 325 } 326 // sb = _types[i].appendBriefDescription(sb); 327 String sig = _types[i].getGenericSignature(); 328 sb.append(sig); 329 } 330 sb.append('>'); 331 return sb.toString(); 332 } 333 hashCode()334 @Override public int hashCode() { return _hashCode; } 335 equals(Object o)336 @Override public boolean equals(Object o) 337 { 338 if (o == this) return true; 339 if (!ClassUtil.hasClass(o, getClass())) { 340 return false; 341 } 342 TypeBindings other = (TypeBindings) o; 343 int len = _types.length; 344 if (len != other.size()) { 345 return false; 346 } 347 JavaType[] otherTypes = other._types; 348 for (int i = 0; i < len; ++i) { 349 if (!otherTypes[i].equals(_types[i])) { 350 return false; 351 } 352 } 353 return true; 354 } 355 356 /* 357 /********************************************************************** 358 /* Package accessible methods 359 /********************************************************************** 360 */ 361 typeParameterArray()362 protected JavaType[] typeParameterArray() { 363 return _types; 364 } 365 366 /* 367 /********************************************************************** 368 /* Helper classes 369 /********************************************************************** 370 */ 371 372 // 30-Oct-2015, tatu: Surprising, but looks like type parameters access can be bit of 373 // a hot spot. So avoid for a small number of common generic types. Note that we do 374 // need both common abstract types and concrete ones; latter for specialization 375 376 /** 377 * Helper class that contains simple logic for avoiding repeated lookups via 378 * {@link Class#getTypeParameters()} as that can be a performance issue for 379 * some use cases (wasteful, usually one-off or not reusing mapper). 380 * Partly isolated to avoid initialization for cases where no generic types are 381 * used. 382 */ 383 static class TypeParamStash { 384 private final static TypeVariable<?>[] VARS_ABSTRACT_LIST = AbstractList.class.getTypeParameters(); 385 private final static TypeVariable<?>[] VARS_COLLECTION = Collection.class.getTypeParameters(); 386 private final static TypeVariable<?>[] VARS_ITERABLE = Iterable.class.getTypeParameters(); 387 private final static TypeVariable<?>[] VARS_LIST = List.class.getTypeParameters(); 388 private final static TypeVariable<?>[] VARS_ARRAY_LIST = ArrayList.class.getTypeParameters(); 389 390 private final static TypeVariable<?>[] VARS_MAP = Map.class.getTypeParameters(); 391 private final static TypeVariable<?>[] VARS_HASH_MAP = HashMap.class.getTypeParameters(); 392 private final static TypeVariable<?>[] VARS_LINKED_HASH_MAP = LinkedHashMap.class.getTypeParameters(); 393 paramsFor1(Class<?> erasedType)394 public static TypeVariable<?>[] paramsFor1(Class<?> erasedType) 395 { 396 if (erasedType == Collection.class) { 397 return VARS_COLLECTION; 398 } 399 if (erasedType == List.class) { 400 return VARS_LIST; 401 } 402 if (erasedType == ArrayList.class) { 403 return VARS_ARRAY_LIST; 404 } 405 if (erasedType == AbstractList.class) { 406 return VARS_ABSTRACT_LIST; 407 } 408 if (erasedType == Iterable.class) { 409 return VARS_ITERABLE; 410 } 411 return erasedType.getTypeParameters(); 412 } 413 paramsFor2(Class<?> erasedType)414 public static TypeVariable<?>[] paramsFor2(Class<?> erasedType) 415 { 416 if (erasedType == Map.class) { 417 return VARS_MAP; 418 } 419 if (erasedType == HashMap.class) { 420 return VARS_HASH_MAP; 421 } 422 if (erasedType == LinkedHashMap.class) { 423 return VARS_LINKED_HASH_MAP; 424 } 425 return erasedType.getTypeParameters(); 426 } 427 } 428 429 /** 430 * Helper type used to allow caching of generic types 431 * 432 * @since 2.8 433 */ 434 final static class AsKey { 435 private final Class<?> _raw; 436 private final JavaType[] _params; 437 private final int _hash; 438 AsKey(Class<?> raw, JavaType[] params, int hash)439 public AsKey(Class<?> raw, JavaType[] params, int hash) { 440 _raw = raw ; 441 _params = params; 442 _hash = hash; 443 } 444 445 @Override hashCode()446 public int hashCode() { return _hash; } 447 448 @Override equals(Object o)449 public boolean equals(Object o) { 450 if (o == this) return true; 451 if (o == null) return false; 452 if (o.getClass() != getClass()) return false; 453 AsKey other = (AsKey) o; 454 455 if ((_hash == other._hash) && (_raw == other._raw)) { 456 final JavaType[] otherParams = other._params; 457 final int len = _params.length; 458 459 if (len == otherParams.length) { 460 for (int i = 0; i < len; ++i) { 461 if (!_params[i].equals(otherParams[i])) { 462 return false; 463 } 464 } 465 return true; 466 } 467 } 468 return false; 469 } 470 471 @Override toString()472 public String toString() { 473 return _raw.getName()+"<>"; 474 } 475 } 476 } 477