BasicClassIntrospector.java

package tools.jackson.databind.introspect;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

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

public class BasicClassIntrospector
    extends ClassIntrospector
    implements java.io.Serializable
{
    private static final long serialVersionUID = 3L;

    private final static Class<?> CLS_OBJECT = Object.class;
    private final static Class<?> CLS_STRING = String.class;
    private final static Class<?> CLS_NUMBER = Number.class;
    private final static Class<?> CLS_JSON_NODE = JsonNode.class;

    /* We keep a small set of pre-constructed descriptions to use for
     * common non-structured values, such as Numbers and Strings.
     * This is strictly performance optimization to reduce what is
     * usually one-time cost, but seems useful for some cases considering
     * simplicity.
     */
    private final static AnnotatedClass OBJECT_AC = new AnnotatedClass(CLS_OBJECT);
    private final static AnnotatedClass STRING_AC = new AnnotatedClass(CLS_STRING);

    private final static AnnotatedClass BOOLEAN_AC = new AnnotatedClass(Boolean.TYPE);
    private final static AnnotatedClass INT_AC = new AnnotatedClass(Integer.TYPE);
    private final static AnnotatedClass LONG_AC = new AnnotatedClass(Long.TYPE);

    private final static AnnotatedClass NUMBER_AC = new AnnotatedClass(CLS_NUMBER);

    /*
    /**********************************************************************
    /* Configuration
    /**********************************************************************
     */

    protected final MixInResolver _mixInResolver;

    protected final MapperConfig<?> _config;

    /*
    /**********************************************************************
    /* State
    /**********************************************************************
     */

    /**
     * Reuse fully-resolved annotations during a single operation
     */
    protected HashMap<JavaType, AnnotatedClass> _resolvedFullAnnotations;

    // 15-Oct-2019, tatu: No measurable benefit from trying to reuse direct
    //    annotation access.
//    protected HashMap<JavaType, AnnotatedClass> _resolvedDirectAnnotations;

    /**
     * Reuse full bean descriptions for serialization during a single operation
     */
    protected HashMap<JavaType, BasicBeanDescription> _resolvedSerBeanDescs;

    /**
     * Reuse full bean descriptions for serialization during a single operation
     */
    protected HashMap<JavaType, BasicBeanDescription> _resolvedDeserBeanDescs;

    /*
    /**********************************************************************
    /* Life cycle
    /**********************************************************************
     */

    public BasicClassIntrospector() {
        _config = null;
        _mixInResolver = null;
    }

    protected BasicClassIntrospector(MapperConfig<?> config) {
        _config = Objects.requireNonNull(config, "Cannot pass null `config`");
        _mixInResolver = config;
    }

    @Override
    public BasicClassIntrospector forMapper() {
        // 14-Oct-2019, tatu: no per-mapper caching used, so just return as-is
        return this;
    }

    @Override
    public BasicClassIntrospector forOperation(MapperConfig<?> config) {
        return new BasicClassIntrospector(config);
    }
    /*
    /**********************************************************************
    /* Factory method impls: annotation resolution
    /**********************************************************************
     */

    @Override
    public AnnotatedClass introspectClassAnnotations(JavaType type)
    {
        AnnotatedClass ac = _findStdTypeDef(type);
        if (ac != null) {
//System.err.println(" AC.introspectClassAnnotations "+type.getRawClass().getSimpleName()+" -> std-def");
            return ac;
        }
        if (_resolvedFullAnnotations == null) {
            _resolvedFullAnnotations = new HashMap<>();
        } else {
            ac = _resolvedFullAnnotations.get(type);
            if (ac != null) {
//System.err.println(" AC.introspectClassAnnotations "+type.getRawClass().getSimpleName()+" -> CACHED");
                return ac;
            }
        }
//System.err.println(" AC.introspectClassAnnotations "+type.getRawClass().getSimpleName()+" -> resolve");
        ac = _resolveAnnotatedClass(type);
        _resolvedFullAnnotations.put(type, ac);
        return ac;
    }

    @Override
    public AnnotatedClass introspectDirectClassAnnotations(JavaType type)
    {
        AnnotatedClass ac = _findStdTypeDef(type);
        return (ac != null) ? ac : _resolveAnnotatedWithoutSuperTypes(type);
    }

    protected AnnotatedClass _resolveAnnotatedClass(JavaType type) {
        return AnnotatedClassResolver.resolve(_config, type, _mixInResolver);
    }

    protected AnnotatedClass _resolveAnnotatedWithoutSuperTypes(JavaType type) {
        return AnnotatedClassResolver.resolveWithoutSuperTypes(_config, type, _mixInResolver);
    }

    /*
    /**********************************************************************
    /* Factory method impls: bean introspection
    /**********************************************************************
     */

    @Override
    public BasicBeanDescription introspectForSerialization(JavaType type,
            AnnotatedClass classDef)
    {
        // minor optimization: for some JDK types do minimal introspection
        BasicBeanDescription desc = _findStdTypeDesc(type);
        if (desc == null) {
            // As per [databind#550], skip full introspection for some of standard
            // structured types as well
            desc = _findStdJdkCollectionDesc(type);
            if (desc == null) {
                if (_resolvedSerBeanDescs == null) {
                    _resolvedSerBeanDescs = new HashMap<>();
                } else {
                    desc = _resolvedSerBeanDescs.get(type);
                    if (desc != null) {
                        return desc;
                    }
                }
                desc = BasicBeanDescription.forSerialization(collectProperties(type,
                        classDef, true, "set"));
                _resolvedSerBeanDescs.put(type, desc);
            }
        }
        return desc;
    }

    @Override
    public BasicBeanDescription introspectForDeserialization(JavaType type,
            AnnotatedClass classDef)
    {
        // minor optimization: for some JDK types do minimal introspection
        BasicBeanDescription desc = _findStdTypeDesc(type);
        if (desc == null) {
            // As per [databind#550], skip full introspection for some of standard
            // structured types as well
            desc = _findStdJdkCollectionDesc(type);
            if (desc == null) {
                if (_resolvedDeserBeanDescs == null) {
                    _resolvedDeserBeanDescs = new HashMap<>();
                } else {
                    desc = _resolvedDeserBeanDescs.get(type);
                    if (desc != null) {
                        return desc;
                    }
                }
                desc = BasicBeanDescription.forDeserialization(collectProperties(type,
                        classDef, false, "set"));
                _resolvedDeserBeanDescs.put(type, desc);
            }
        }
        return desc;
    }

    @Override
    public BasicBeanDescription introspectForDeserializationWithBuilder(JavaType type,
            BeanDescription valueTypeDesc)
    {
        // no std JDK types with Builders, so:
        return BasicBeanDescription.forDeserialization(collectPropertiesWithBuilder(type,
                introspectClassAnnotations(type), valueTypeDesc,
                false));
    }

    @Override
    public BasicBeanDescription introspectForCreation(JavaType type,
            AnnotatedClass classDef)
    {
        BasicBeanDescription desc = _findStdTypeDesc(type);
        if (desc == null) {
            // As per [databind#550], skip full introspection for some of standard
            // structured types as well
            desc = _findStdJdkCollectionDesc(type);
            if (desc == null) {
                desc = BasicBeanDescription.forDeserialization(collectProperties(type,
                        classDef, false, "set"));
            }
        }
        return desc;
    }

    /*
    /**********************************************************************
    /* Overridable helper methods
    /**********************************************************************
     */

    protected POJOPropertiesCollector collectProperties(JavaType type, AnnotatedClass classDef,
            boolean forSerialization, String mutatorPrefix)
    {
        final AccessorNamingStrategy accNaming = type.isRecordType()
                ? _config.getAccessorNaming().forRecord(_config, classDef)
                : _config.getAccessorNaming().forPOJO(_config, classDef);
        return constructPropertyCollector(type, classDef, forSerialization, accNaming);
    }

    protected POJOPropertiesCollector collectPropertiesWithBuilder(JavaType type,
            AnnotatedClass builderClassDef, BeanDescription valueTypeDesc,
            boolean forSerialization)
    {
        final AccessorNamingStrategy accNaming = _config.getAccessorNaming().forBuilder(_config,
                builderClassDef, valueTypeDesc);
        return constructPropertyCollector(type, builderClassDef, forSerialization, accNaming);
    }

    /**
     * Overridable method called for creating {@link POJOPropertiesCollector} instance
     * to use; override is needed if a custom sub-class is to be used.
     */
    protected POJOPropertiesCollector constructPropertyCollector(JavaType type, AnnotatedClass classDef,
            boolean forSerialization, AccessorNamingStrategy accNaming)
    {
        return new POJOPropertiesCollector(_config, forSerialization, type, classDef, accNaming);
    }

    protected BasicBeanDescription _findStdTypeDesc(JavaType type) {
        AnnotatedClass ac = _findStdTypeDef(type);
        return (ac == null) ? null : BasicBeanDescription.forOtherUse(_config, type, ac);
    }

    /**
     * Method called to see if type is one of core JDK types
     * that we have cached for efficiency.
     */
    protected AnnotatedClass _findStdTypeDef(JavaType type)
    {
        final Class<?> rawType = type.getRawClass();
        if (rawType.isPrimitive()) {
            if (rawType == Integer.TYPE) {
                return INT_AC;
            }
            if (rawType == Long.TYPE) {
                return LONG_AC;
            }
            if (rawType == Boolean.TYPE) {
                return BOOLEAN_AC;
            }
        } else if (ClassUtil.isJDKClass(rawType)) {
            if (rawType == CLS_STRING) {
                return STRING_AC;
            }
            // Should be ok to just pass "primitive" info
            if (rawType == Integer.class) {
                return INT_AC;
            }
            if (rawType == Long.class) {
                return LONG_AC;
            }
            if (rawType == Boolean.class) {
                return BOOLEAN_AC;
            }

            if (rawType == CLS_OBJECT) {
                return OBJECT_AC;
            }
            // This mostly matters for "untyped" deserialization
            if (rawType == CLS_NUMBER) {
                return NUMBER_AC;
            }
        } else if (CLS_JSON_NODE.isAssignableFrom(rawType)) {
            return new AnnotatedClass(rawType);
        }
        return null;
    }

    protected BasicBeanDescription _findStdJdkCollectionDesc(JavaType type)
    {
        if (_isStdJDKCollection(type)) {
            return BasicBeanDescription.forOtherUse(_config, type,
                    introspectClassAnnotations(type));
        }
        return null;
    }

    /**
     * Helper method used to decide whether we can omit introspection
     * for members (methods, fields, constructors); we may do so for
     * a limited number of container types JDK provides.
     */
    private boolean _isStdJDKCollection(JavaType type)
    {
        if (!type.isContainerType() || type.isArrayType()) {
            return false;
        }
        Class<?> raw = type.getRawClass();
        if (ClassUtil.isJDKClass(raw)) {
            // 23-Sep-2014, tatu: Should we be conservative here (minimal number
            //    of matches), or ambitious? Let's do latter for now.
            if (Collection.class.isAssignableFrom(raw)
                    || Map.class.isAssignableFrom(raw)) {
                // 28-May-2024, tatu: Complications wrt [databind#4515] / [databind#2795]
                //    mean that partial introspection NOT for inner classes
                if (raw.toString().indexOf('$') > 0) {
                    return false;
                }
                return true;
            }
        }
        return false;
    }
}