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.type.TypeFactory;
16 import com.fasterxml.jackson.databind.util.ClassUtil;
17
18 /**
19  * Helper class used to contain details of how Creators (annotated constructors
20  * and static methods) are discovered to be accessed by and via {@link AnnotatedClass}.
21  *
22  * @since 2.9
23  */

24 final class AnnotatedCreatorCollector
25     extends CollectorBase
26 {
27     // // // Configuration
28
29     private final TypeResolutionContext _typeContext;
30
31     // @since 2.11.3
32     private final TypeFactory _typeFactory;
33
34     /**
35      * @since 2.11
36      */

37     private final boolean _collectAnnotations;
38
39     // // // Collected state
40
41     private AnnotatedConstructor _defaultConstructor;
42
43     AnnotatedCreatorCollector(AnnotationIntrospector intr, TypeFactory tf,
44             TypeResolutionContext tc, boolean collectAnnotations)
45     {
46         super(intr);
47         _typeFactory = tf;
48         _typeContext = tc;
49         _collectAnnotations = collectAnnotations;
50     }
51
52     @Deprecated // since 2.11.3; to be removed ASAP (2.12.0)
53     public static Creators collectCreators(AnnotationIntrospector intr,
54             TypeResolutionContext tc,
55             JavaType type, Class<?> primaryMixIn, boolean collectAnnotations)
56     {
57         return collectCreators(intr, TypeFactory.defaultInstance(),
58                 tc, type, primaryMixIn, collectAnnotations);
59     }
60
61     /**
62      * @since 2.11.3
63      */

64     public static Creators collectCreators(AnnotationIntrospector intr,
65             TypeFactory typeFactory, TypeResolutionContext tc,
66             JavaType type, Class<?> primaryMixIn, boolean collectAnnotations)
67     {
68         // 30-Sep-2020, tatu: [databind#2795] Even if annotations not otherwise
69         //  requested (for JDK collections), force change if mix-in in use
70         collectAnnotations |= (primaryMixIn != null);
71
72         // Constructor also always members of resolved class, parent == resolution context
73         return new AnnotatedCreatorCollector(intr, typeFactory, tc, collectAnnotations)
74                 .collect(type, primaryMixIn);
75     }
76
77     Creators collect(JavaType type, Class<?> primaryMixIn)
78     {
79     // 30-Apr-2016, tatu: [databind#1215]: Actually, while truethis does
80     //   NOT apply to context since sub-class may have type bindings
81 //        TypeResolutionContext typeContext = new TypeResolutionContext.Basic(_typeFactory, _type.getBindings());
82
83         List<AnnotatedConstructor> constructors = _findPotentialConstructors(type, primaryMixIn);
84         List<AnnotatedMethod> factories = _findPotentialFactories(type, primaryMixIn);
85
86         /* And then... let's remove all constructors that are deemed
87          * ignorable after all annotations have been properly collapsed.
88          */

89         // AnnotationIntrospector is null if annotations not enabled; if so, can skip:
90         if (_collectAnnotations) {
91             if (_defaultConstructor != null) {
92                 if (_intr.hasIgnoreMarker(_defaultConstructor)) {
93                     _defaultConstructor = null;
94                 }
95             }
96             // count down to allow safe removal
97             for (int i = constructors.size(); --i >= 0; ) {
98                 if (_intr.hasIgnoreMarker(constructors.get(i))) {
99                     constructors.remove(i);
100                 }
101             }
102             for (int i = factories.size(); --i >= 0; ) {
103                 if (_intr.hasIgnoreMarker(factories.get(i))) {
104                     factories.remove(i);
105                 }
106             }
107         }
108         return new AnnotatedClass.Creators(_defaultConstructor, constructors, factories);
109     }
110
111     /**
112      * Helper method for locating constructors (and matching mix-in overrides)
113      * we might want to use; this is needed in order to mix information between
114      * the two and construct resulting {@link AnnotatedConstructor}s
115      */

116     private List<AnnotatedConstructor> _findPotentialConstructors(JavaType type,
117             Class<?> primaryMixIn)
118     {
119         ClassUtil.Ctor defaultCtor = null;
120         List<ClassUtil.Ctor> ctors = null;
121
122         // 18-Jun-2016, tatu: Enum constructors will never be useful (unlike
123         //    possibly static factory methods); but they can be royal PITA
124         //    due to some oddities by JVM; see:
125         //    [https://github.com/FasterXML/jackson-module-parameter-names/issues/35]
126         //    for more. So, let's just skip them.
127         if (!type.isEnumType()) {
128             ClassUtil.Ctor[] declaredCtors = ClassUtil.getConstructors(type.getRawClass());
129             for (ClassUtil.Ctor ctor : declaredCtors) {
130                 if (!isIncludableConstructor(ctor.getConstructor())) {
131                     continue;
132                 }
133                 if (ctor.getParamCount() == 0) {
134                     defaultCtor = ctor;
135                 } else {
136                     if (ctors == null) {
137                         ctors = new ArrayList<>();
138                     }
139                     ctors.add(ctor);
140                 }
141             }
142         }
143         List<AnnotatedConstructor> result;
144         int ctorCount;
145         if (ctors == null) {
146             result = Collections.emptyList();
147             // Nothing found? Short-circuit
148             if (defaultCtor == null) { 
149                 return result;
150             }
151             ctorCount = 0;
152         } else {
153             ctorCount = ctors.size();
154             result = new ArrayList<>(ctorCount);
155             for (int i = 0; i < ctorCount; ++i) {
156                 result.add(null);
157             }
158         }
159
160         // so far so good; but do we also need to find mix-ins overrides?
161         if (primaryMixIn != null) {
162             MemberKey[] ctorKeys = null;
163             for (ClassUtil.Ctor mixinCtor : ClassUtil.getConstructors(primaryMixIn)) {
164                 if (mixinCtor.getParamCount() == 0) {
165                     if (defaultCtor != null) {
166                         _defaultConstructor = constructDefaultConstructor(defaultCtor, mixinCtor);
167                         defaultCtor = null;
168                     }
169                     continue;
170                 }
171                 if (ctors != null) {
172                     if (ctorKeys == null) {
173                         ctorKeys = new MemberKey[ctorCount];
174                         for (int i = 0; i < ctorCount; ++i) {
175                             ctorKeys[i] = new MemberKey(ctors.get(i).getConstructor());
176                         }
177                     }
178                     MemberKey key = new MemberKey(mixinCtor.getConstructor());
179     
180                     for (int i = 0; i < ctorCount; ++i) {
181                         if (key.equals(ctorKeys[i])) {
182                             result.set(i,
183                                     constructNonDefaultConstructor(ctors.get(i), mixinCtor));
184                             break;
185                         }
186                     }
187                 }
188             }
189         }
190         // Ok: anything within mix-ins has been resolved; anything remaining we must resolve
191         if (defaultCtor != null) {
192             _defaultConstructor = constructDefaultConstructor(defaultCtor, null);
193         }
194         for (int i = 0; i < ctorCount; ++i) {
195             AnnotatedConstructor ctor = result.get(i);
196             if (ctor == null) {
197                 result.set(i,
198                         constructNonDefaultConstructor(ctors.get(i), null));
199             }
200         }
201         return result;
202     }
203
204     private List<AnnotatedMethod> _findPotentialFactories(JavaType type, Class<?> primaryMixIn)
205     {
206         List<Method> candidates = null;
207
208         // First find all potentially relevant static methods
209         for (Method m : ClassUtil.getClassMethods(type.getRawClass())) {
210             if (!Modifier.isStatic(m.getModifiers())) {
211                 continue;
212             }
213             // all factory methods are fine:
214             //int argCount = m.getParameterTypes().length;
215             if (candidates == null) {
216                 candidates = new ArrayList<>();
217             }
218             candidates.add(m);
219         }
220         // and then locate mix-ins, if any
221         if (candidates == null) {
222             return Collections.emptyList();
223         }
224         // 05-Sep-2020, tatu: Important fix wrt [databind#2821] -- static methods
225         //   do NOT have type binding context of the surrounding class and although
226         //   passing that should not break things, it appears to... Regardless,
227         //   it should not be needed or useful as those bindings are only available
228         //   to non-static members
229         TypeResolutionContext typeResCtxt = new TypeResolutionContext.Empty(_typeFactory);
230
231         int factoryCount = candidates.size();
232         List<AnnotatedMethod> result = new ArrayList<>(factoryCount);
233         for (int i = 0; i < factoryCount; ++i) {
234             result.add(null);
235         }
236         // so far so good; but do we also need to find mix-ins overrides?
237         if (primaryMixIn != null) {
238             MemberKey[] methodKeys = null;
239             for (Method mixinFactory : primaryMixIn.getDeclaredMethods()) {
240                 if (!Modifier.isStatic(mixinFactory.getModifiers())) {
241                     continue;
242                 }
243                 if (methodKeys == null) {
244                     methodKeys = new MemberKey[factoryCount];
245                     for (int i = 0; i < factoryCount; ++i) {
246                         methodKeys[i] = new MemberKey(candidates.get(i));
247                     }
248                 }
249                 MemberKey key = new MemberKey(mixinFactory);
250                 for (int i = 0; i < factoryCount; ++i) {
251                     if (key.equals(methodKeys[i])) {
252                         result.set(i,
253                                 constructFactoryCreator(candidates.get(i),
254                                         typeResCtxt, mixinFactory));
255                         break;
256                     }
257                 }
258             }
259         }
260         // Ok: anything within mix-ins has been resolved; anything remaining we must resolve
261         for (int i = 0; i < factoryCount; ++i) {
262             AnnotatedMethod factory = result.get(i);
263             if (factory == null) {
264                 result.set(i,
265                         constructFactoryCreator(candidates.get(i),
266                                 typeResCtxt, null));
267             }
268         }
269         return result;
270     }
271
272     protected AnnotatedConstructor constructDefaultConstructor(ClassUtil.Ctor ctor,
273             ClassUtil.Ctor mixin)
274     {
275         return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
276                 collectAnnotations(ctor, mixin),
277                 // 16-Jun-2019, tatu: default is zero-args, so can't have parameter annotations
278                 NO_ANNOTATION_MAPS);
279     }
280
281     protected AnnotatedConstructor constructNonDefaultConstructor(ClassUtil.Ctor ctor,
282             ClassUtil.Ctor mixin)
283     {
284         final int paramCount = ctor.getParamCount();
285         if (_intr == null) { // when annotation processing is disabled
286             return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
287                     _emptyAnnotationMap(), _emptyAnnotationMaps(paramCount));
288         }
289
290         /* Looks like JDK has discrepancy, whereas annotations for implicit 'this'
291          * (for non-static inner classes) are NOT included, but type is?
292          * Strange, sounds like a bug. Alas, we can't really fix that...
293          */

294         if (paramCount == 0) { // no-arg default constructors, can simplify slightly
295             return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
296                     collectAnnotations(ctor, mixin),
297                     NO_ANNOTATION_MAPS);
298         }
299         // Also: enum value constructors
300         AnnotationMap[] resolvedAnnotations;
301         Annotation[][] paramAnns = ctor.getParameterAnnotations();
302         if (paramCount != paramAnns.length) {
303             // Limits of the work-around (to avoid hiding real errors):
304             // first, only applicable for member classes and then either:
305
306             resolvedAnnotations = null;
307             Class<?> dc = ctor.getDeclaringClass();
308             // (a) is enum, which have two extra hidden params (name, index)
309             if (ClassUtil.isEnumType(dc) && (paramCount == paramAnns.length + 2)) {
310                 Annotation[][] old = paramAnns;
311                 paramAnns = new Annotation[old.length+2][];
312                 System.arraycopy(old, 0, paramAnns, 2, old.length);
313                 resolvedAnnotations = collectAnnotations(paramAnns, null);
314             } else if (dc.isMemberClass()) {
315                 // (b) non-static inner classes, get implicit 'thisfor parameter, not  annotation
316                 if (paramCount == (paramAnns.length + 1)) {
317                     // hack attack: prepend a null entry to make things match
318                     Annotation[][] old = paramAnns;
319                     paramAnns = new Annotation[old.length+1][];
320                     System.arraycopy(old, 0, paramAnns, 1, old.length);
321                     paramAnns[0] = NO_ANNOTATIONS;
322                     resolvedAnnotations = collectAnnotations(paramAnns, null);
323                 }
324             }
325             if (resolvedAnnotations == null) {
326                 throw new IllegalStateException(String.format(
327 "Internal error: constructor for %s has mismatch: %d parameters; %d sets of annotations",
328 ctor.getDeclaringClass().getName(), paramCount, paramAnns.length));
329             }
330         } else {
331             resolvedAnnotations = collectAnnotations(paramAnns,
332                     (mixin == null) ? null : mixin.getParameterAnnotations());
333         }
334         return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
335                 collectAnnotations(ctor, mixin), resolvedAnnotations);
336     }
337
338     protected AnnotatedMethod constructFactoryCreator(Method m,
339             TypeResolutionContext typeResCtxt, Method mixin)
340     {
341         final int paramCount = m.getParameterTypes().length;
342         if (_intr == null) { // when annotation processing is disabled
343             return new AnnotatedMethod(typeResCtxt, m, _emptyAnnotationMap(),
344                     _emptyAnnotationMaps(paramCount));
345         }
346         if (paramCount == 0) { // common enough we can slightly optimize
347             return new AnnotatedMethod(typeResCtxt, m, collectAnnotations(m, mixin),
348                     NO_ANNOTATION_MAPS);
349         }
350         return new AnnotatedMethod(typeResCtxt, m, collectAnnotations(m, mixin),
351                 collectAnnotations(m.getParameterAnnotations(),
352                         (mixin == null) ? null : mixin.getParameterAnnotations()));
353     }
354
355     private AnnotationMap[] collectAnnotations(Annotation[][] mainAnns, Annotation[][] mixinAnns) {
356         if (_collectAnnotations) {
357             final int count = mainAnns.length;
358             AnnotationMap[] result = new AnnotationMap[count];
359             for (int i = 0; i < count; ++i) {
360                 AnnotationCollector c = collectAnnotations(AnnotationCollector.emptyCollector(),
361                         mainAnns[i]);
362                 if (mixinAnns != null) {
363                     c = collectAnnotations(c, mixinAnns[i]);
364                 }
365                 result[i] = c.asAnnotationMap();
366             }
367             return result;
368         }
369         return NO_ANNOTATION_MAPS;
370     }
371
372     // // NOTE: these are only called when we know we have AnnotationIntrospector
373
374     private AnnotationMap collectAnnotations(ClassUtil.Ctor main, ClassUtil.Ctor mixin) {
375         if (_collectAnnotations) {
376             AnnotationCollector c = collectAnnotations(main.getDeclaredAnnotations());
377             if (mixin != null) {
378                 c = collectAnnotations(c, mixin.getDeclaredAnnotations());
379             }
380             return c.asAnnotationMap();
381         }
382         return _emptyAnnotationMap();
383     }
384
385     private final AnnotationMap collectAnnotations(AnnotatedElement main, AnnotatedElement mixin) {
386         AnnotationCollector c = collectAnnotations(main.getDeclaredAnnotations());
387         if (mixin != null) {
388             c = collectAnnotations(c, mixin.getDeclaredAnnotations());
389         }
390         return c.asAnnotationMap();
391     }
392
393     // for [databind#1005]: do not use or expose synthetic constructors
394     private static boolean isIncludableConstructor(Constructor<?> c) {
395         return !c.isSynthetic();
396     }
397 }
398