com.fasterxml.jackson.databind.introspect.AnnotatedCreatorCollector Maven / Gradle / Ivy
The newest version!
package com.fasterxml.jackson.databind.introspect;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass.Creators;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Helper class used to contain details of how Creators (annotated constructors
* and static methods) are discovered to be accessed by and via {@link AnnotatedClass}.
*
* @since 2.9
*/
final class AnnotatedCreatorCollector
extends CollectorBase
{
// // // Configuration
private final TypeResolutionContext _typeContext;
/**
* @since 2.11
*/
private final boolean _collectAnnotations;
// // // Collected state
private AnnotatedConstructor _defaultConstructor;
AnnotatedCreatorCollector(AnnotationIntrospector intr,
TypeResolutionContext tc, boolean collectAnnotations)
{
super(intr);
_typeContext = tc;
_collectAnnotations = collectAnnotations;
}
/**
* @since 2.11.3
*/
public static Creators collectCreators(AnnotationIntrospector intr,
TypeFactory typeFactory, TypeResolutionContext tc,
JavaType type, Class> primaryMixIn, boolean collectAnnotations)
{
// 30-Sep-2020, tatu: [databind#2795] Even if annotations not otherwise
// requested (for JDK collections), force change if mix-in in use
collectAnnotations |= (primaryMixIn != null);
// Constructor also always members of resolved class, parent == resolution context
return new AnnotatedCreatorCollector(intr, tc, collectAnnotations)
.collect(typeFactory, type, primaryMixIn);
}
Creators collect(TypeFactory typeFactory, JavaType type, Class> primaryMixIn)
{
// 30-Apr-2016, tatu: [databind#1215]: Actually, while true, this does
// NOT apply to context since sub-class may have type bindings
// TypeResolutionContext typeContext = new TypeResolutionContext.Basic(_typeFactory, _type.getBindings());
List constructors = _findPotentialConstructors(type, primaryMixIn);
List factories = _findPotentialFactories(typeFactory, type, primaryMixIn);
/* And then... let's remove all constructors that are deemed
* ignorable after all annotations have been properly collapsed.
*/
// AnnotationIntrospector is null if annotations not enabled; if so, can skip:
if (_collectAnnotations) {
if (_defaultConstructor != null) {
if (_intr.hasIgnoreMarker(_defaultConstructor)) {
_defaultConstructor = null;
}
}
// count down to allow safe removal
for (int i = constructors.size(); --i >= 0; ) {
if (_intr.hasIgnoreMarker(constructors.get(i))) {
constructors.remove(i);
}
}
for (int i = factories.size(); --i >= 0; ) {
if (_intr.hasIgnoreMarker(factories.get(i))) {
factories.remove(i);
}
}
}
return new AnnotatedClass.Creators(_defaultConstructor, constructors, factories);
}
/**
* Helper method for locating constructors (and matching mix-in overrides)
* we might want to use; this is needed in order to mix information between
* the two and construct resulting {@link AnnotatedConstructor}s
*/
private List _findPotentialConstructors(JavaType type,
Class> primaryMixIn)
{
ClassUtil.Ctor defaultCtor = null;
List ctors = null;
// 18-Jun-2016, tatu: Enum constructors will never be useful (unlike
// possibly static factory methods); but they can be royal PITA
// due to some oddities by JVM; see:
// [https://github.com/FasterXML/jackson-module-parameter-names/issues/35]
// for more. So, let's just skip them.
if (!type.isEnumType()) {
ClassUtil.Ctor[] declaredCtors = ClassUtil.getConstructors(type.getRawClass());
for (ClassUtil.Ctor ctor : declaredCtors) {
if (!isIncludableConstructor(ctor.getConstructor())) {
continue;
}
if (ctor.getParamCount() == 0) {
defaultCtor = ctor;
} else {
if (ctors == null) {
ctors = new ArrayList<>();
}
ctors.add(ctor);
}
}
}
List result;
int ctorCount;
if (ctors == null) {
result = Collections.emptyList();
// Nothing found? Short-circuit
if (defaultCtor == null) {
return result;
}
ctorCount = 0;
} else {
ctorCount = ctors.size();
result = new ArrayList<>(ctorCount);
for (int i = 0; i < ctorCount; ++i) {
result.add(null);
}
}
// so far so good; but do we also need to find mix-ins overrides?
if (primaryMixIn != null) {
MemberKey[] ctorKeys = null;
for (ClassUtil.Ctor mixinCtor : ClassUtil.getConstructors(primaryMixIn)) {
if (mixinCtor.getParamCount() == 0) {
if (defaultCtor != null) {
_defaultConstructor = constructDefaultConstructor(defaultCtor, mixinCtor);
defaultCtor = null;
}
continue;
}
if (ctors != null) {
if (ctorKeys == null) {
ctorKeys = new MemberKey[ctorCount];
for (int i = 0; i < ctorCount; ++i) {
ctorKeys[i] = new MemberKey(ctors.get(i).getConstructor());
}
}
MemberKey key = new MemberKey(mixinCtor.getConstructor());
for (int i = 0; i < ctorCount; ++i) {
if (key.equals(ctorKeys[i])) {
result.set(i,
constructNonDefaultConstructor(ctors.get(i), mixinCtor));
break;
}
}
}
}
}
// Ok: anything within mix-ins has been resolved; anything remaining we must resolve
if (defaultCtor != null) {
_defaultConstructor = constructDefaultConstructor(defaultCtor, null);
}
for (int i = 0; i < ctorCount; ++i) {
AnnotatedConstructor ctor = result.get(i);
if (ctor == null) {
result.set(i,
constructNonDefaultConstructor(ctors.get(i), null));
}
}
return result;
}
private List _findPotentialFactories(TypeFactory typeFactory,
JavaType type, Class> primaryMixIn)
{
List candidates = null;
// First find all potentially relevant static methods
for (Method m : ClassUtil.getClassMethods(type.getRawClass())) {
if (!_isIncludableFactoryMethod(m)) {
continue;
}
// all factory methods are fine:
//int argCount = m.getParameterTypes().length;
if (candidates == null) {
candidates = new ArrayList<>();
}
candidates.add(m);
}
// and then locate mix-ins, if any
if (candidates == null) {
return Collections.emptyList();
}
// 05-Sep-2020, tatu: Important fix wrt [databind#2821] -- static methods
// do NOT have type binding context of the surrounding class and although
// passing that should not break things, it appears to... Regardless,
// it should not be needed or useful as those bindings are only available
// to non-static members
// final TypeResolutionContext typeResCtxt = new TypeResolutionContext.Empty(typeFactory);
// 27-Oct-2020, tatu: SIGH. As per [databind#2894] there is widespread use of
// incorrect bindings in the wild -- not supported (no tests) but used
// nonetheless. So, for 2.11.x, put back "Bad Bindings"...
//
// 03-Nov-2020, ckozak: Implement generic JsonCreator TypeVariable handling [databind#2895]
// final TypeResolutionContext emptyTypeResCtxt = new TypeResolutionContext.Empty(typeFactory);
// 23-Aug-2021, tatu: Double-d'oh! As per [databind#3220], we must revert to
// the Incorrect Illogical But Used By Users Bindings... for 2.x at least.
final TypeResolutionContext initialTypeResCtxt = _typeContext;
int factoryCount = candidates.size();
List result = new ArrayList<>(factoryCount);
for (int i = 0; i < factoryCount; ++i) {
result.add(null);
}
// so far so good; but do we also need to find mix-ins overrides?
if (primaryMixIn != null) {
MemberKey[] methodKeys = null;
for (Method mixinFactory : primaryMixIn.getDeclaredMethods()) {
if (!_isIncludableFactoryMethod(mixinFactory)) {
continue;
}
if (methodKeys == null) {
methodKeys = new MemberKey[factoryCount];
for (int i = 0; i < factoryCount; ++i) {
methodKeys[i] = new MemberKey(candidates.get(i));
}
}
MemberKey key = new MemberKey(mixinFactory);
for (int i = 0; i < factoryCount; ++i) {
if (key.equals(methodKeys[i])) {
result.set(i,
constructFactoryCreator(candidates.get(i),
initialTypeResCtxt, mixinFactory));
break;
}
}
}
}
// Ok: anything within mix-ins has been resolved; anything remaining we must resolve
for (int i = 0; i < factoryCount; ++i) {
AnnotatedMethod factory = result.get(i);
if (factory == null) {
Method candidate = candidates.get(i);
// 06-Nov-2020, tatu: Fix from [databind#2895] will try to resolve
// nominal static method type bindings into expected target type
// (if generic types involved)
// 23-Aug-2021, tatu: ... is this still needed, wrt un-fix in [databind#3220]?
TypeResolutionContext typeResCtxt = MethodGenericTypeResolver.narrowMethodTypeParameters(
candidate, type, typeFactory, initialTypeResCtxt);
result.set(i,
constructFactoryCreator(candidate, typeResCtxt, null));
}
}
return result;
}
private static boolean _isIncludableFactoryMethod(Method m)
{
if (!Modifier.isStatic(m.getModifiers())) {
return false;
}
// 09-Nov-2020, ckozak: Avoid considering synthetic methods such as
// lambdas used within methods because they're not relevant.
return !m.isSynthetic();
}
protected AnnotatedConstructor constructDefaultConstructor(ClassUtil.Ctor ctor,
ClassUtil.Ctor mixin)
{
return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
collectAnnotations(ctor, mixin),
// 16-Jun-2019, tatu: default is zero-args, so can't have parameter annotations
NO_ANNOTATION_MAPS);
}
protected AnnotatedConstructor constructNonDefaultConstructor(ClassUtil.Ctor ctor,
ClassUtil.Ctor mixin)
{
final int paramCount = ctor.getParamCount();
if (_intr == null) { // when annotation processing is disabled
return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
_emptyAnnotationMap(), _emptyAnnotationMaps(paramCount));
}
/* Looks like JDK has discrepancy, whereas annotations for implicit 'this'
* (for non-static inner classes) are NOT included, but type is?
* Strange, sounds like a bug. Alas, we can't really fix that...
*/
if (paramCount == 0) { // no-arg default constructors, can simplify slightly
return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
collectAnnotations(ctor, mixin),
NO_ANNOTATION_MAPS);
}
// Also: enum value constructors
AnnotationMap[] resolvedAnnotations;
Annotation[][] paramAnns = ctor.getParameterAnnotations();
if (paramCount != paramAnns.length) {
// Limits of the work-around (to avoid hiding real errors):
// first, only applicable for member classes and then either:
resolvedAnnotations = null;
Class> dc = ctor.getDeclaringClass();
// (a) is enum, which have two extra hidden params (name, index)
if (ClassUtil.isEnumType(dc) && (paramCount == paramAnns.length + 2)) {
Annotation[][] old = paramAnns;
paramAnns = new Annotation[old.length+2][];
System.arraycopy(old, 0, paramAnns, 2, old.length);
resolvedAnnotations = collectAnnotations(paramAnns, null);
} else if (dc.isMemberClass()) {
// (b) non-static inner classes, get implicit 'this' for parameter, not annotation
if (paramCount == (paramAnns.length + 1)) {
// hack attack: prepend a null entry to make things match
Annotation[][] old = paramAnns;
paramAnns = new Annotation[old.length+1][];
System.arraycopy(old, 0, paramAnns, 1, old.length);
paramAnns[0] = NO_ANNOTATIONS;
resolvedAnnotations = collectAnnotations(paramAnns, null);
}
}
if (resolvedAnnotations == null) {
throw new IllegalStateException(String.format(
"Internal error: constructor for %s has mismatch: %d parameters; %d sets of annotations",
ctor.getDeclaringClass().getName(), paramCount, paramAnns.length));
}
} else {
resolvedAnnotations = collectAnnotations(paramAnns,
(mixin == null) ? null : mixin.getParameterAnnotations());
}
return new AnnotatedConstructor(_typeContext, ctor.getConstructor(),
collectAnnotations(ctor, mixin), resolvedAnnotations);
}
protected AnnotatedMethod constructFactoryCreator(Method m,
TypeResolutionContext typeResCtxt, Method mixin)
{
final int paramCount = m.getParameterCount();
if (_intr == null) { // when annotation processing is disabled
return new AnnotatedMethod(typeResCtxt, m, _emptyAnnotationMap(),
_emptyAnnotationMaps(paramCount));
}
if (paramCount == 0) { // common enough we can slightly optimize
return new AnnotatedMethod(typeResCtxt, m, collectAnnotations(m, mixin),
NO_ANNOTATION_MAPS);
}
return new AnnotatedMethod(typeResCtxt, m, collectAnnotations(m, mixin),
collectAnnotations(m.getParameterAnnotations(),
(mixin == null) ? null : mixin.getParameterAnnotations()));
}
private AnnotationMap[] collectAnnotations(Annotation[][] mainAnns, Annotation[][] mixinAnns) {
if (_collectAnnotations) {
final int count = mainAnns.length;
AnnotationMap[] result = new AnnotationMap[count];
for (int i = 0; i < count; ++i) {
AnnotationCollector c = collectAnnotations(AnnotationCollector.emptyCollector(),
mainAnns[i]);
if (mixinAnns != null) {
c = collectAnnotations(c, mixinAnns[i]);
}
result[i] = c.asAnnotationMap();
}
return result;
}
return NO_ANNOTATION_MAPS;
}
// // NOTE: these are only called when we know we have AnnotationIntrospector
private AnnotationMap collectAnnotations(ClassUtil.Ctor main, ClassUtil.Ctor mixin) {
if (_collectAnnotations) {
AnnotationCollector c = collectAnnotations(main.getDeclaredAnnotations());
if (mixin != null) {
c = collectAnnotations(c, mixin.getDeclaredAnnotations());
}
return c.asAnnotationMap();
}
return _emptyAnnotationMap();
}
private final AnnotationMap collectAnnotations(AnnotatedElement main, AnnotatedElement mixin) {
AnnotationCollector c = collectAnnotations(main.getDeclaredAnnotations());
if (mixin != null) {
c = collectAnnotations(c, mixin.getDeclaredAnnotations());
}
return c.asAnnotationMap();
}
// for [databind#1005]: do not use or expose synthetic constructors
private static boolean isIncludableConstructor(Constructor> c) {
return !c.isSynthetic();
}
}