package com.fasterxml.jackson.databind.introspect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.introspect.ClassIntrospector.MixInResolver;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.ClassUtil;

public class AnnotatedMethodCollector
    extends CollectorBase
{
    private final MixInResolver _mixInResolver;

    
Since:2.11
/** * @since 2.11 */
private final boolean _collectAnnotations; AnnotatedMethodCollector(AnnotationIntrospector intr, MixInResolver mixins, boolean collectAnnotations) { super(intr); _mixInResolver = (intr == null) ? null : mixins; _collectAnnotations = collectAnnotations; } public static AnnotatedMethodMap collectMethods(AnnotationIntrospector intr, TypeResolutionContext tc, MixInResolver mixins, TypeFactory types, JavaType type, List<JavaType> superTypes, Class<?> primaryMixIn, boolean collectAnnotations) { // Constructor also always members of resolved class, parent == resolution context return new AnnotatedMethodCollector(intr, mixins, collectAnnotations) .collect(types, tc, type, superTypes, primaryMixIn); } AnnotatedMethodMap collect(TypeFactory typeFactory, TypeResolutionContext tc, JavaType mainType, List<JavaType> superTypes, Class<?> primaryMixIn) { Map<MemberKey,MethodBuilder> methods = new LinkedHashMap<>(); // first: methods from the class itself _addMemberMethods(tc, mainType.getRawClass(), methods, primaryMixIn); // and then augment these with annotations from super-types: for (JavaType type : superTypes) { Class<?> mixin = (_mixInResolver == null) ? null : _mixInResolver.findMixInClassFor(type.getRawClass()); _addMemberMethods( new TypeResolutionContext.Basic(typeFactory, type.getBindings()), type.getRawClass(), methods, mixin); } // Special case: mix-ins for Object.class? (to apply to ALL classes) boolean checkJavaLangObject = false; if (_mixInResolver != null) { Class<?> mixin = _mixInResolver.findMixInClassFor(Object.class); if (mixin != null) { _addMethodMixIns(tc, mainType.getRawClass(), methods, mixin); //, mixins); checkJavaLangObject = true; } } // Any unmatched mix-ins? Most likely error cases (not matching any method); // but there is one possible real use case: exposing Object#hashCode // (alas, Object#getClass can NOT be exposed) // Since we only know of that ONE case, optimize for it if (checkJavaLangObject && (_intr != null) && !methods.isEmpty()) { // Could use lookup but probably as fast or faster to traverse for (Map.Entry<MemberKey,MethodBuilder> entry : methods.entrySet()) { MemberKey k = entry.getKey(); if (!"hashCode".equals(k.getName()) || (0 != k.argCount())) { continue; } try { // And with that, we can generate it appropriately Method m = Object.class.getDeclaredMethod(k.getName()); if (m != null) { MethodBuilder b = entry.getValue(); b.annotations = collectDefaultAnnotations(b.annotations, m.getDeclaredAnnotations()); b.method = m; } } catch (Exception e) { } } } // And then let's create the lookup map if (methods.isEmpty()) { return new AnnotatedMethodMap(); } Map<MemberKey,AnnotatedMethod> actual = new LinkedHashMap<>(methods.size()); for (Map.Entry<MemberKey,MethodBuilder> entry : methods.entrySet()) { AnnotatedMethod am = entry.getValue().build(); if (am != null) { actual.put(entry.getKey(), am); } } return new AnnotatedMethodMap(actual); } private void _addMemberMethods(TypeResolutionContext tc, Class<?> cls, Map<MemberKey,MethodBuilder> methods, Class<?> mixInCls) { // first, mixIns, since they have higher priority then class methods if (mixInCls != null) { _addMethodMixIns(tc, cls, methods, mixInCls); } if (cls == null) { // just so caller need not check when passing super-class return; } // then methods from the class itself for (Method m : ClassUtil.getClassMethods(cls)) { if (!_isIncludableMemberMethod(m)) { continue; } final MemberKey key = new MemberKey(m); MethodBuilder b = methods.get(key); if (b == null) { AnnotationCollector c = (_intr == null) ? AnnotationCollector.emptyCollector() : collectAnnotations(m.getDeclaredAnnotations()); methods.put(key, new MethodBuilder(tc, m, c)); } else { if (_collectAnnotations) { b.annotations = collectDefaultAnnotations(b.annotations, m.getDeclaredAnnotations()); } Method old = b.method; if (old == null) { // had "mix-over", replace b.method = m; // } else if (old.getDeclaringClass().isInterface() && !m.getDeclaringClass().isInterface()) { } else if (Modifier.isAbstract(old.getModifiers()) && !Modifier.isAbstract(m.getModifiers())) { // 06-Jan-2010, tatu: Except that if method we saw first is // from an interface, and we now find a non-interface definition, we should // use this method, but with combination of annotations. // This helps (or rather, is essential) with JAXB annotations and // may also result in faster method calls (interface calls are slightly // costlier than regular method calls) b.method = m; // 23-Aug-2017, tatu: [databind#1705] Also need to change the type resolution context if so // (note: mix-over case above shouldn't need it) b.typeContext = tc; } } } } protected void _addMethodMixIns(TypeResolutionContext tc, Class<?> targetClass, Map<MemberKey,MethodBuilder> methods, Class<?> mixInCls) { if (_intr == null) { return; } for (Class<?> mixin : ClassUtil.findRawSuperTypes(mixInCls, targetClass, true)) { for (Method m : mixin.getDeclaredMethods()) { if (!_isIncludableMemberMethod(m)) { continue; } final MemberKey key = new MemberKey(m); MethodBuilder b = methods.get(key); Annotation[] anns = m.getDeclaredAnnotations(); if (b == null) { // nothing yet; add but do NOT specify method -- this marks it // as "mix-over", floating mix-in methods.put(key, new MethodBuilder(tc, null, collectAnnotations(anns))); } else { b.annotations = collectDefaultAnnotations(b.annotations, anns); } } } } private boolean _isIncludableMemberMethod(Method m) { if (Modifier.isStatic(m.getModifiers()) // Looks like generics can introduce hidden bridge and/or synthetic methods. // I don't think we want to consider those... || m.isSynthetic() || m.isBridge()) { return false; } // also, for now we have no use for methods with more than 2 arguments: // (2 argument methods for "any setter", fwtw) int pcount = m.getParameterTypes().length; return (pcount <= 2); } private final static class MethodBuilder { public TypeResolutionContext typeContext; // Method left empty for "floating" mix-in, filled in as need be public Method method; public AnnotationCollector annotations; public MethodBuilder(TypeResolutionContext tc, Method m, AnnotationCollector ann) { typeContext = tc; method = m; annotations = ann; } public AnnotatedMethod build() { if (method == null) { return null; } // 12-Apr-2017, tatu: Note that parameter annotations are NOT collected -- we could // collect them if that'd make sense but... return new AnnotatedMethod(typeContext, method, annotations.asAnnotationMap(), null); } } }