| 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.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; |
| |
| // // // Collected state |
| |
| private AnnotatedConstructor _defaultConstructor; |
| |
| AnnotatedCreatorCollector(AnnotationIntrospector intr, |
| TypeResolutionContext tc) |
| { |
| super(intr); |
| _typeContext = tc; |
| } |
| |
| public static Creators collectCreators(AnnotationIntrospector intr, |
| TypeResolutionContext tc, |
| JavaType type, Class<?> primaryMixIn) |
| { |
| // Constructor also always members of resolved class, parent == resolution context |
| return new AnnotatedCreatorCollector(intr, tc) |
| .collect(type, primaryMixIn); |
| } |
| |
| Creators collect(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<AnnotatedConstructor> constructors = _findPotentialConstructors(type, primaryMixIn); |
| List<AnnotatedMethod> factories = _findPotentialFactories(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 (_intr != null) { |
| 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<AnnotatedConstructor> _findPotentialConstructors(JavaType type, |
| Class<?> primaryMixIn) |
| { |
| ClassUtil.Ctor defaultCtor = null; |
| List<ClassUtil.Ctor> 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<AnnotatedConstructor> 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<AnnotatedMethod> _findPotentialFactories(JavaType type, Class<?> primaryMixIn) |
| { |
| List<Method> candidates = null; |
| |
| // First find all potentially relevant static methods |
| for (Method m : ClassUtil.getClassMethods(type.getRawClass())) { |
| if (!Modifier.isStatic(m.getModifiers())) { |
| 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(); |
| } |
| int factoryCount = candidates.size(); |
| List<AnnotatedMethod> 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 : ClassUtil.getDeclaredMethods(primaryMixIn)) { |
| if (!Modifier.isStatic(mixinFactory.getModifiers())) { |
| 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), 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) { |
| result.set(i, |
| constructFactoryCreator(candidates.get(i), null)); |
| } |
| } |
| return result; |
| } |
| |
| protected AnnotatedConstructor constructDefaultConstructor(ClassUtil.Ctor ctor, |
| ClassUtil.Ctor mixin) |
| { |
| if (_intr == null) { // when annotation processing is disabled |
| return new AnnotatedConstructor(_typeContext, ctor.getConstructor(), |
| _emptyAnnotationMap(), NO_ANNOTATION_MAPS); |
| } |
| return new AnnotatedConstructor(_typeContext, ctor.getConstructor(), |
| collectAnnotations(ctor, mixin), |
| collectAnnotations(ctor.getConstructor().getParameterAnnotations(), |
| (mixin == null) ? null : mixin.getConstructor().getParameterAnnotations())); |
| } |
| |
| 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 (dc.isEnum() && (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, Method mixin) |
| { |
| final int paramCount = m.getParameterTypes().length; |
| if (_intr == null) { // when annotation processing is disabled |
| return new AnnotatedMethod(_typeContext, m, _emptyAnnotationMap(), |
| _emptyAnnotationMaps(paramCount)); |
| } |
| if (paramCount == 0) { // common enough we can slightly optimize |
| return new AnnotatedMethod(_typeContext, m, collectAnnotations(m, mixin), |
| NO_ANNOTATION_MAPS); |
| } |
| return new AnnotatedMethod(_typeContext, m, collectAnnotations(m, mixin), |
| collectAnnotations(m.getParameterAnnotations(), |
| (mixin == null) ? null : mixin.getParameterAnnotations())); |
| } |
| |
| private AnnotationMap[] collectAnnotations(Annotation[][] mainAnns, Annotation[][] mixinAnns) { |
| 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; |
| } |
| |
| // // NOTE: these are only called when we know we have AnnotationIntrospector |
| |
| private AnnotationMap collectAnnotations(ClassUtil.Ctor main, ClassUtil.Ctor mixin) { |
| AnnotationCollector c = collectAnnotations(main.getConstructor().getDeclaredAnnotations()); |
| if (mixin != null) { |
| c = collectAnnotations(c, mixin.getConstructor().getDeclaredAnnotations()); |
| } |
| return c.asAnnotationMap(); |
| } |
| |
| 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(); |
| } |
| } |