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