AnnotatedFieldCollector.java

package tools.jackson.databind.introspect;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

import tools.jackson.databind.JavaType;
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.util.ClassUtil;

public class AnnotatedFieldCollector
    extends CollectorBase
{
    private final MixInResolver _mixInResolver;

    private final boolean _collectAnnotations;

    // // // Collected state

    AnnotatedFieldCollector(MapperConfig<?> config, MixInResolver mixins,
            boolean collectAnnotations)
    {
        super(config);
        _mixInResolver = mixins;
        _collectAnnotations = collectAnnotations;
    }

    public static List<AnnotatedField> collectFields(MapperConfig<?> config,
            TypeResolutionContext tc, MixInResolver mixins,
            JavaType type, Class<?> primaryMixIn, boolean collectAnnotations)
    {
        return new AnnotatedFieldCollector(config, mixins, collectAnnotations)
                .collect(tc, type, primaryMixIn);
    }

    List<AnnotatedField> collect(TypeResolutionContext tc,
            JavaType type, Class<?> primaryMixIn)
    {
        Map<String,FieldBuilder> foundFields = _findFields(tc, type, primaryMixIn, null);
        if (foundFields == null) {
            return Collections.emptyList();
        }
        List<AnnotatedField> result = new ArrayList<>(foundFields.size());
        for (FieldBuilder b : foundFields.values()) {
            result.add(b.build());
        }
        return result;
    }

    private Map<String,FieldBuilder> _findFields(TypeResolutionContext tc,
            JavaType type, Class<?> mixin,
            Map<String,FieldBuilder> fields)
    {
        // First, a quick test: we only care for regular classes (not interfaces,
        //primitive types etc), except for Object.class. A simple check to rule out
        // other cases is to see if there is a super class or not.
        final JavaType parentType = type.getSuperClass();
        if (parentType == null) {
            return fields;
        }
        // Let's add super-class' fields first, then ours.
        {
            Class<?> parentMixin = (_mixInResolver == null) ? null
                    : _mixInResolver.findMixInClassFor(parentType.getRawClass());
            fields = _findFields(new TypeResolutionContext.Basic(_config.getTypeFactory(),
                    parentType.getBindings()),
                    parentType, parentMixin, fields);
        }
        final Class<?> rawType = type.getRawClass();
        for (Field f : rawType.getDeclaredFields()) {
            // static fields not included (transients are at this point, filtered out later)
            if (!_isIncludableField(f)) {
                continue;
            }
            // Ok now: we can (and need) not filter out ignorable fields at this point; partly
            // because mix-ins haven't been added, and partly because logic can be done
            // when determining get/settability of the field.
            if (fields == null) {
                fields = new LinkedHashMap<>();
            }
            FieldBuilder b = new FieldBuilder(tc, f);
            if (_collectAnnotations) {
                b.annotations = collectAnnotations(b.annotations, f.getDeclaredAnnotations());
            }
            fields.put(f.getName(), b);
        }
        // And then... any mix-in overrides?
        if ((fields != null) && (mixin != null)) {
            _addFieldMixIns(mixin, rawType, fields);
        }
        return fields;
    }

    /**
     * Method called to add field mix-ins from given mix-in class (and its fields)
     * into already collected actual fields (from introspected classes and their
     * super-classes)
     */
    private void _addFieldMixIns(Class<?> mixInCls, Class<?> targetClass,
            Map<String,FieldBuilder> fields)
    {
        List<Class<?>> parents = ClassUtil.findSuperClasses(mixInCls, targetClass, true);
        for (Class<?> mixin : parents) {
            for (Field mixinField : mixin.getDeclaredFields()) {
                // there are some dummy things (static, synthetic); better ignore
                if (!_isIncludableField(mixinField)) {
                    continue;
                }
                String name = mixinField.getName();
                // anything to mask? (if not, quietly ignore)
                FieldBuilder b = fields.get(name);
                if (b != null) {
                    b.annotations = collectAnnotations(b.annotations, mixinField.getDeclaredAnnotations());
                }
            }
        }
    }

    private boolean _isIncludableField(Field f)
    {
        // [databind#2787]: To allow `Enum` mixins, need to include Enum constants even
        //  to they are static fields, not instance ones.
        if (f.isEnumConstant()) {
            return true;
        }
        // Most likely synthetic fields, if any, are to be skipped similar to methods
        if (f.isSynthetic()) {
            return false;
        }
        // Static fields are never included (except for above-mentioned Enum constants.
        // Transient are (since 2.6), for  purpose of propagating removal
        if (Modifier.isStatic(f.getModifiers())) {
            return false;
        }
        return true;
    }

    private final static class FieldBuilder {
        public final TypeResolutionContext typeContext;
        public final Field field;

        public AnnotationCollector annotations;

        public FieldBuilder(TypeResolutionContext tc, Field f) {
            typeContext = tc;
            field = f;
            annotations = AnnotationCollector.emptyCollector();
        }

        public AnnotatedField build() {
            return new AnnotatedField(typeContext, field, annotations.asAnnotationMap());
        }
    }
}