1 package com.fasterxml.jackson.databind.introspect;
2 
3 import java.lang.annotation.Annotation;
4 import java.lang.reflect.AnnotatedElement;
5 import java.lang.reflect.Constructor;
6 import java.lang.reflect.Method;
7 import java.lang.reflect.Modifier;
8 import java.util.ArrayList;
9 import java.util.Collections;
10 import java.util.List;
11 
12 import com.fasterxml.jackson.databind.AnnotationIntrospector;
13 import com.fasterxml.jackson.databind.JavaType;
14 import com.fasterxml.jackson.databind.introspect.AnnotatedClass.Creators;
15 import com.fasterxml.jackson.databind.util.ClassUtil;
16 
17 /**
18  * Helper class used to contain details of how Creators (annotated constructors
19  * and static methods) are discovered to be accessed by and via {@link AnnotatedClass}.
20  *
21  * @since 2.9
22  */
23 final class AnnotatedCreatorCollector
24     extends CollectorBase
25 {
26     // // // Configuration
27 
28     private final TypeResolutionContext _typeContext;
29 
30     /**
31      * @since 2.11
32      */
33     private final boolean _collectAnnotations;
34 
35     // // // Collected state
36 
37     private AnnotatedConstructor _defaultConstructor;
38 
AnnotatedCreatorCollector(AnnotationIntrospector intr, TypeResolutionContext tc, boolean collectAnnotations)39     AnnotatedCreatorCollector(AnnotationIntrospector intr,
40             TypeResolutionContext tc, boolean collectAnnotations)
41     {
42         super(intr);
43         _typeContext = tc;
44         _collectAnnotations = collectAnnotations;
45     }
46 
collectCreators(AnnotationIntrospector intr, TypeResolutionContext tc, JavaType type, Class<?> primaryMixIn, boolean collectAnnotations)47     public static Creators collectCreators(AnnotationIntrospector intr,
48             TypeResolutionContext tc,
49             JavaType type, Class<?> primaryMixIn, boolean collectAnnotations)
50     {
51         final boolean checkClassAnnotations = (intr != null)
52                 && !ClassUtil.isJDKClass(type.getRawClass());
53 
54         // Constructor also always members of resolved class, parent == resolution context
55         return new AnnotatedCreatorCollector(intr, tc, checkClassAnnotations)
56                 .collect(type, primaryMixIn);
57     }
58 
collect(JavaType type, Class<?> primaryMixIn)59     Creators collect(JavaType type, Class<?> primaryMixIn)
60     {
61     // 30-Apr-2016, tatu: [databind#1215]: Actually, while true, this does
62     //   NOT apply to context since sub-class may have type bindings
63 //        TypeResolutionContext typeContext = new TypeResolutionContext.Basic(_typeFactory, _type.getBindings());
64 
65         List<AnnotatedConstructor> constructors = _findPotentialConstructors(type, primaryMixIn);
66         List<AnnotatedMethod> factories = _findPotentialFactories(type, primaryMixIn);
67 
68         /* And then... let's remove all constructors that are deemed
69          * ignorable after all annotations have been properly collapsed.
70          */
71         // AnnotationIntrospector is null if annotations not enabled; if so, can skip:
72         if (_collectAnnotations) {
73             if (_defaultConstructor != null) {
74                 if (_intr.hasIgnoreMarker(_defaultConstructor)) {
75                     _defaultConstructor = null;
76                 }
77             }
78             // count down to allow safe removal
79             for (int i = constructors.size(); --i >= 0; ) {
80                 if (_intr.hasIgnoreMarker(constructors.get(i))) {
81                     constructors.remove(i);
82                 }
83             }
84             for (int i = factories.size(); --i >= 0; ) {
85                 if (_intr.hasIgnoreMarker(factories.get(i))) {
86                     factories.remove(i);
87                 }
88             }
89         }
90         return new AnnotatedClass.Creators(_defaultConstructor, constructors, factories);
91     }
92 
93     /**
94      * Helper method for locating constructors (and matching mix-in overrides)
95      * we might want to use; this is needed in order to mix information between
96      * the two and construct resulting {@link AnnotatedConstructor}s
97      */
_findPotentialConstructors(JavaType type, Class<?> primaryMixIn)98     private List<AnnotatedConstructor> _findPotentialConstructors(JavaType type,
99             Class<?> primaryMixIn)
100     {
101         ClassUtil.Ctor defaultCtor = null;
102         List<ClassUtil.Ctor> ctors = null;
103 
104         // 18-Jun-2016, tatu: Enum constructors will never be useful (unlike
105         //    possibly static factory methods); but they can be royal PITA
106         //    due to some oddities by JVM; see:
107         //    [https://github.com/FasterXML/jackson-module-parameter-names/issues/35]
108         //    for more. So, let's just skip them.
109         if (!type.isEnumType()) {
110             ClassUtil.Ctor[] declaredCtors = ClassUtil.getConstructors(type.getRawClass());
111             for (ClassUtil.Ctor ctor : declaredCtors) {
112                 if (!isIncludableConstructor(ctor.getConstructor())) {
113                     continue;
114                 }
115                 if (ctor.getParamCount() == 0) {
116                     defaultCtor = ctor;
117                 } else {
118                     if (ctors == null) {
119                         ctors = new ArrayList<>();
120                     }
121                     ctors.add(ctor);
122                 }
123             }
124         }
125         List<AnnotatedConstructor> result;
126         int ctorCount;
127         if (ctors == null) {
128             result = Collections.emptyList();
129             // Nothing found? Short-circuit
130             if (defaultCtor == null) {
131                 return result;
132             }
133             ctorCount = 0;
134         } else {
135             ctorCount = ctors.size();
136             result = new ArrayList<>(ctorCount);
137             for (int i = 0; i < ctorCount; ++i) {
138                 result.add(null);
139             }
140         }
141 
142         // so far so good; but do we also need to find mix-ins overrides?
143         if (primaryMixIn != null) {
144             MemberKey[] ctorKeys = null;
145             for (ClassUtil.Ctor mixinCtor : ClassUtil.getConstructors(primaryMixIn)) {
146                 if (mixinCtor.getParamCount() == 0) {
147                     if (defaultCtor != null) {
148                         _defaultConstructor = constructDefaultConstructor(defaultCtor, mixinCtor);
149                         defaultCtor = null;
150                     }
151                     continue;
152                 }
153                 if (ctors != null) {
154                     if (ctorKeys == null) {
155                         ctorKeys = new MemberKey[ctorCount];
156                         for (int i = 0; i < ctorCount; ++i) {
157                             ctorKeys[i] = new MemberKey(ctors.get(i).getConstructor());
158                         }
159                     }
160                     MemberKey key = new MemberKey(mixinCtor.getConstructor());
161 
162                     for (int i = 0; i < ctorCount; ++i) {
163                         if (key.equals(ctorKeys[i])) {
164                             result.set(i,
165                                     constructNonDefaultConstructor(ctors.get(i), mixinCtor));
166                             break;
167                         }
168                     }
169                 }
170             }
171         }
172         // Ok: anything within mix-ins has been resolved; anything remaining we must resolve
173         if (defaultCtor != null) {
174             _defaultConstructor = constructDefaultConstructor(defaultCtor, null);
175         }
176         for (int i = 0; i < ctorCount; ++i) {
177             AnnotatedConstructor ctor = result.get(i);
178             if (ctor == null) {
179                 result.set(i,
180                         constructNonDefaultConstructor(ctors.get(i), null));
181             }
182         }
183         return result;
184     }
185 
_findPotentialFactories(JavaType type, Class<?> primaryMixIn)186     private List<AnnotatedMethod> _findPotentialFactories(JavaType type, Class<?> primaryMixIn)
187     {
188         List<Method> candidates = null;
189 
190         // First find all potentially relevant static methods
191         for (Method m : ClassUtil.getClassMethods(type.getRawClass())) {
192             if (!Modifier.isStatic(m.getModifiers())) {
193                 continue;
194             }
195             // all factory methods are fine:
196             //int argCount = m.getParameterTypes().length;
197             if (candidates == null) {
198                 candidates = new ArrayList<>();
199             }
200             candidates.add(m);
201         }
202         // and then locate mix-ins, if any
203         if (candidates == null) {
204             return Collections.emptyList();
205         }
206         int factoryCount = candidates.size();
207         List<AnnotatedMethod> result = new ArrayList<>(factoryCount);
208         for (int i = 0; i < factoryCount; ++i) {
209             result.add(null);
210         }
211         // so far so good; but do we also need to find mix-ins overrides?
212         if (primaryMixIn != null) {
213             MemberKey[] methodKeys = null;
214             for (Method mixinFactory : primaryMixIn.getDeclaredMethods()) {
215                 if (!Modifier.isStatic(mixinFactory.getModifiers())) {
216                     continue;
217                 }
218                 if (methodKeys == null) {
219                     methodKeys = new MemberKey[factoryCount];
220                     for (int i = 0; i < factoryCount; ++i) {
221                         methodKeys[i] = new MemberKey(candidates.get(i));
222                     }
223                 }
224                 MemberKey key = new MemberKey(mixinFactory);
225                 for (int i = 0; i < factoryCount; ++i) {
226                     if (key.equals(methodKeys[i])) {
227                         result.set(i,
228                                 constructFactoryCreator(candidates.get(i), mixinFactory));
229                         break;
230                     }
231                 }
232             }
233         }
234         // Ok: anything within mix-ins has been resolved; anything remaining we must resolve
235         for (int i = 0; i < factoryCount; ++i) {
236             AnnotatedMethod factory = result.get(i);
237             if (factory == null) {
238                 result.set(i,
239                         constructFactoryCreator(candidates.get(i), null));
240             }
241         }
242         return result;
243     }
244 
constructDefaultConstructor(ClassUtil.Ctor ctor, ClassUtil.Ctor mixin)245     protected AnnotatedConstructor constructDefaultConstructor(ClassUtil.Ctor ctor,
246             ClassUtil.Ctor mixin)
247     {
248         return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
249                 collectAnnotations(ctor, mixin),
250                 // 16-Jun-2019, tatu: default is zero-args, so can't have parameter annotations
251                 NO_ANNOTATION_MAPS);
252     }
253 
constructNonDefaultConstructor(ClassUtil.Ctor ctor, ClassUtil.Ctor mixin)254     protected AnnotatedConstructor constructNonDefaultConstructor(ClassUtil.Ctor ctor,
255             ClassUtil.Ctor mixin)
256     {
257         final int paramCount = ctor.getParamCount();
258         if (_intr == null) { // when annotation processing is disabled
259             return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
260                     _emptyAnnotationMap(), _emptyAnnotationMaps(paramCount));
261         }
262 
263         /* Looks like JDK has discrepancy, whereas annotations for implicit 'this'
264          * (for non-static inner classes) are NOT included, but type is?
265          * Strange, sounds like a bug. Alas, we can't really fix that...
266          */
267         if (paramCount == 0) { // no-arg default constructors, can simplify slightly
268             return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
269                     collectAnnotations(ctor, mixin),
270                     NO_ANNOTATION_MAPS);
271         }
272         // Also: enum value constructors
273         AnnotationMap[] resolvedAnnotations;
274         Annotation[][] paramAnns = ctor.getParameterAnnotations();
275         if (paramCount != paramAnns.length) {
276             // Limits of the work-around (to avoid hiding real errors):
277             // first, only applicable for member classes and then either:
278 
279             resolvedAnnotations = null;
280             Class<?> dc = ctor.getDeclaringClass();
281             // (a) is enum, which have two extra hidden params (name, index)
282             if (ClassUtil.isEnumType(dc) && (paramCount == paramAnns.length + 2)) {
283                 Annotation[][] old = paramAnns;
284                 paramAnns = new Annotation[old.length+2][];
285                 System.arraycopy(old, 0, paramAnns, 2, old.length);
286                 resolvedAnnotations = collectAnnotations(paramAnns, null);
287             } else if (dc.isMemberClass()) {
288                 // (b) non-static inner classes, get implicit 'this' for parameter, not  annotation
289                 if (paramCount == (paramAnns.length + 1)) {
290                     // hack attack: prepend a null entry to make things match
291                     Annotation[][] old = paramAnns;
292                     paramAnns = new Annotation[old.length+1][];
293                     System.arraycopy(old, 0, paramAnns, 1, old.length);
294                     paramAnns[0] = NO_ANNOTATIONS;
295                     resolvedAnnotations = collectAnnotations(paramAnns, null);
296                 }
297             }
298             if (resolvedAnnotations == null) {
299                 throw new IllegalStateException(String.format(
300 "Internal error: constructor for %s has mismatch: %d parameters; %d sets of annotations",
301 ctor.getDeclaringClass().getName(), paramCount, paramAnns.length));
302             }
303         } else {
304             resolvedAnnotations = collectAnnotations(paramAnns,
305                     (mixin == null) ? null : mixin.getParameterAnnotations());
306         }
307         return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
308                 collectAnnotations(ctor, mixin), resolvedAnnotations);
309     }
310 
constructFactoryCreator(Method m, Method mixin)311     protected AnnotatedMethod constructFactoryCreator(Method m, Method mixin)
312     {
313         final int paramCount = m.getParameterTypes().length;
314         if (_intr == null) { // when annotation processing is disabled
315             return new AnnotatedMethod(_typeContext, m, _emptyAnnotationMap(),
316                     _emptyAnnotationMaps(paramCount));
317         }
318         if (paramCount == 0) { // common enough we can slightly optimize
319             return new AnnotatedMethod(_typeContext, m, collectAnnotations(m, mixin),
320                     NO_ANNOTATION_MAPS);
321         }
322         return new AnnotatedMethod(_typeContext, m, collectAnnotations(m, mixin),
323                 collectAnnotations(m.getParameterAnnotations(),
324                         (mixin == null) ? null : mixin.getParameterAnnotations()));
325     }
326 
collectAnnotations(Annotation[][] mainAnns, Annotation[][] mixinAnns)327     private AnnotationMap[] collectAnnotations(Annotation[][] mainAnns, Annotation[][] mixinAnns) {
328         if (_collectAnnotations) {
329             final int count = mainAnns.length;
330             AnnotationMap[] result = new AnnotationMap[count];
331             for (int i = 0; i < count; ++i) {
332                 AnnotationCollector c = collectAnnotations(AnnotationCollector.emptyCollector(),
333                         mainAnns[i]);
334                 if (mixinAnns != null) {
335                     c = collectAnnotations(c, mixinAnns[i]);
336                 }
337                 result[i] = c.asAnnotationMap();
338             }
339             return result;
340         }
341         return NO_ANNOTATION_MAPS;
342     }
343 
344     // // NOTE: these are only called when we know we have AnnotationIntrospector
345 
collectAnnotations(ClassUtil.Ctor main, ClassUtil.Ctor mixin)346     private AnnotationMap collectAnnotations(ClassUtil.Ctor main, ClassUtil.Ctor mixin) {
347         if (_collectAnnotations) {
348             AnnotationCollector c = collectAnnotations(main.getDeclaredAnnotations());
349             if (mixin != null) {
350                 c = collectAnnotations(c, mixin.getDeclaredAnnotations());
351             }
352             return c.asAnnotationMap();
353         }
354         return _emptyAnnotationMap();
355     }
356 
collectAnnotations(AnnotatedElement main, AnnotatedElement mixin)357     private final AnnotationMap collectAnnotations(AnnotatedElement main, AnnotatedElement mixin) {
358         AnnotationCollector c = collectAnnotations(main.getDeclaredAnnotations());
359         if (mixin != null) {
360             c = collectAnnotations(c, mixin.getDeclaredAnnotations());
361         }
362         return c.asAnnotationMap();
363     }
364 
365     // for [databind#1005]: do not use or expose synthetic constructors
isIncludableConstructor(Constructor<?> c)366     private static boolean isIncludableConstructor(Constructor<?> c) {
367         return !c.isSynthetic();
368     }
369 }
370