• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.introspect;
2 
3 import java.lang.annotation.Annotation;
4 import java.lang.reflect.Method;
5 import java.lang.reflect.Modifier;
6 import java.util.*;
7 
8 import com.fasterxml.jackson.databind.AnnotationIntrospector;
9 import com.fasterxml.jackson.databind.JavaType;
10 import com.fasterxml.jackson.databind.introspect.ClassIntrospector.MixInResolver;
11 import com.fasterxml.jackson.databind.type.TypeFactory;
12 import com.fasterxml.jackson.databind.util.ClassUtil;
13 
14 public class AnnotatedMethodCollector
15     extends CollectorBase
16 {
17     private final MixInResolver _mixInResolver;
18 
19     /**
20      * @since 2.11
21      */
22     private final boolean _collectAnnotations;
23 
AnnotatedMethodCollector(AnnotationIntrospector intr, MixInResolver mixins, boolean collectAnnotations)24     AnnotatedMethodCollector(AnnotationIntrospector intr,
25             MixInResolver mixins, boolean collectAnnotations)
26     {
27         super(intr);
28         _mixInResolver = (intr == null) ? null : mixins;
29         _collectAnnotations = collectAnnotations;
30     }
31 
collectMethods(AnnotationIntrospector intr, TypeResolutionContext tc, MixInResolver mixins, TypeFactory types, JavaType type, List<JavaType> superTypes, Class<?> primaryMixIn, boolean collectAnnotations)32     public static AnnotatedMethodMap collectMethods(AnnotationIntrospector intr,
33             TypeResolutionContext tc,
34             MixInResolver mixins, TypeFactory types,
35             JavaType type, List<JavaType> superTypes, Class<?> primaryMixIn,
36             boolean collectAnnotations)
37     {
38         // Constructor also always members of resolved class, parent == resolution context
39         return new AnnotatedMethodCollector(intr, mixins, collectAnnotations)
40                 .collect(types, tc, type, superTypes, primaryMixIn);
41     }
42 
collect(TypeFactory typeFactory, TypeResolutionContext tc, JavaType mainType, List<JavaType> superTypes, Class<?> primaryMixIn)43     AnnotatedMethodMap collect(TypeFactory typeFactory, TypeResolutionContext tc,
44             JavaType mainType, List<JavaType> superTypes, Class<?> primaryMixIn)
45     {
46         Map<MemberKey,MethodBuilder> methods = new LinkedHashMap<>();
47 
48         // first: methods from the class itself
49         _addMemberMethods(tc, mainType.getRawClass(), methods, primaryMixIn);
50 
51         // and then augment these with annotations from super-types:
52         for (JavaType type : superTypes) {
53             Class<?> mixin = (_mixInResolver == null) ? null : _mixInResolver.findMixInClassFor(type.getRawClass());
54             _addMemberMethods(
55                     new TypeResolutionContext.Basic(typeFactory, type.getBindings()),
56                     type.getRawClass(), methods, mixin);
57         }
58         // Special case: mix-ins for Object.class? (to apply to ALL classes)
59         boolean checkJavaLangObject = false;
60         if (_mixInResolver != null) {
61             Class<?> mixin = _mixInResolver.findMixInClassFor(Object.class);
62             if (mixin != null) {
63                 _addMethodMixIns(tc, mainType.getRawClass(), methods, mixin); //, mixins);
64                 checkJavaLangObject = true;
65             }
66         }
67 
68         // Any unmatched mix-ins? Most likely error cases (not matching any method);
69         // but there is one possible real use case: exposing Object#hashCode
70         // (alas, Object#getClass can NOT be exposed)
71         // Since we only know of that ONE case, optimize for it
72         if (checkJavaLangObject && (_intr != null) && !methods.isEmpty()) {
73             // Could use lookup but probably as fast or faster to traverse
74             for (Map.Entry<MemberKey,MethodBuilder> entry : methods.entrySet()) {
75                 MemberKey k = entry.getKey();
76                 if (!"hashCode".equals(k.getName()) || (0 != k.argCount())) {
77                     continue;
78                 }
79                 try {
80                     // And with that, we can generate it appropriately
81                     Method m = Object.class.getDeclaredMethod(k.getName());
82                     if (m != null) {
83                         MethodBuilder b = entry.getValue();
84                         b.annotations = collectDefaultAnnotations(b.annotations,
85                                 m.getDeclaredAnnotations());
86                         b.method = m;
87                    }
88                 } catch (Exception e) { }
89             }
90         }
91 
92         // And then let's create the lookup map
93         if (methods.isEmpty()) {
94             return new AnnotatedMethodMap();
95         }
96         Map<MemberKey,AnnotatedMethod> actual = new LinkedHashMap<>(methods.size());
97         for (Map.Entry<MemberKey,MethodBuilder> entry : methods.entrySet()) {
98             AnnotatedMethod am = entry.getValue().build();
99             if (am != null) {
100                 actual.put(entry.getKey(), am);
101             }
102         }
103         return new AnnotatedMethodMap(actual);
104     }
105 
_addMemberMethods(TypeResolutionContext tc, Class<?> cls, Map<MemberKey,MethodBuilder> methods, Class<?> mixInCls)106     private void _addMemberMethods(TypeResolutionContext tc,
107             Class<?> cls, Map<MemberKey,MethodBuilder> methods, Class<?> mixInCls)
108     {
109         // first, mixIns, since they have higher priority then class methods
110         if (mixInCls != null) {
111             _addMethodMixIns(tc, cls, methods, mixInCls);
112         }
113         if (cls == null) { // just so caller need not check when passing super-class
114             return;
115         }
116         // then methods from the class itself
117         for (Method m : ClassUtil.getClassMethods(cls)) {
118             if (!_isIncludableMemberMethod(m)) {
119                 continue;
120             }
121             final MemberKey key = new MemberKey(m);
122             MethodBuilder b = methods.get(key);
123             if (b == null) {
124                 AnnotationCollector c = (_intr == null) ? AnnotationCollector.emptyCollector()
125                         : collectAnnotations(m.getDeclaredAnnotations());
126                 methods.put(key, new MethodBuilder(tc, m, c));
127             } else {
128                 if (_collectAnnotations) {
129                     b.annotations = collectDefaultAnnotations(b.annotations, m.getDeclaredAnnotations());
130                 }
131                 Method old = b.method;
132                 if (old == null) { // had "mix-over", replace
133                     b.method = m;
134 //                } else if (old.getDeclaringClass().isInterface() && !m.getDeclaringClass().isInterface()) {
135                 } else if (Modifier.isAbstract(old.getModifiers())
136                         && !Modifier.isAbstract(m.getModifiers())) {
137                     // 06-Jan-2010, tatu: Except that if method we saw first is
138                     // from an interface, and we now find a non-interface definition, we should
139                     //   use this method, but with combination of annotations.
140                     //   This helps (or rather, is essential) with JAXB annotations and
141                     //   may also result in faster method calls (interface calls are slightly
142                     //   costlier than regular method calls)
143                     b.method = m;
144                     // 23-Aug-2017, tatu: [databind#1705] Also need to change the type resolution context if so
145                     //    (note: mix-over case above shouldn't need it)
146                     b.typeContext = tc;
147                 }
148             }
149         }
150     }
151 
_addMethodMixIns(TypeResolutionContext tc, Class<?> targetClass, Map<MemberKey,MethodBuilder> methods, Class<?> mixInCls)152     protected void _addMethodMixIns(TypeResolutionContext tc, Class<?> targetClass,
153             Map<MemberKey,MethodBuilder> methods, Class<?> mixInCls)
154     {
155         if (_intr == null) {
156             return;
157         }
158         for (Class<?> mixin : ClassUtil.findRawSuperTypes(mixInCls, targetClass, true)) {
159             for (Method m : mixin.getDeclaredMethods()) {
160                 if (!_isIncludableMemberMethod(m)) {
161                     continue;
162                 }
163                 final MemberKey key = new MemberKey(m);
164                 MethodBuilder b = methods.get(key);
165                 Annotation[] anns = m.getDeclaredAnnotations();
166                 if (b == null) {
167                     // nothing yet; add but do NOT specify method -- this marks it
168                     // as "mix-over", floating mix-in
169                     methods.put(key, new MethodBuilder(tc, null, collectAnnotations(anns)));
170                 } else {
171                     b.annotations = collectDefaultAnnotations(b.annotations, anns);
172                 }
173             }
174         }
175     }
176 
_isIncludableMemberMethod(Method m)177     private boolean _isIncludableMemberMethod(Method m)
178     {
179         if (Modifier.isStatic(m.getModifiers())
180                 // Looks like generics can introduce hidden bridge and/or synthetic methods.
181                 // I don't think we want to consider those...
182                 || m.isSynthetic() || m.isBridge()) {
183             return false;
184         }
185         // also, for now we have no use for methods with more than 2 arguments:
186         // (2 argument methods for "any setter", fwtw)
187         int pcount = m.getParameterTypes().length;
188         return (pcount <= 2);
189     }
190 
191     private final static class MethodBuilder {
192         public TypeResolutionContext typeContext;
193 
194         // Method left empty for "floating" mix-in, filled in as need be
195         public Method method;
196         public AnnotationCollector annotations;
197 
MethodBuilder(TypeResolutionContext tc, Method m, AnnotationCollector ann)198         public MethodBuilder(TypeResolutionContext tc, Method m,
199                 AnnotationCollector ann) {
200             typeContext = tc;
201             method = m;
202             annotations = ann;
203         }
204 
build()205         public AnnotatedMethod build() {
206             if (method == null) {
207                 return null;
208             }
209             // 12-Apr-2017, tatu: Note that parameter annotations are NOT collected -- we could
210             //   collect them if that'd make sense but...
211             return new AnnotatedMethod(typeContext, method, annotations.asAnnotationMap(), null);
212         }
213     }
214 }
215