• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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