com.fasterxml.jackson.databind.introspect.AnnotatedMethodCollector Maven / Gradle / Ivy
The newest version!
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
*/
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 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 superTypes, Class> primaryMixIn)
{
Map 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 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());
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 actual = new LinkedHashMap<>(methods.size());
for (Map.Entry 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 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 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 static 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)
return (m.getParameterCount() <= 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);
}
}
}