BasicBeanDescription.java

package com.fasterxml.jackson.databind.introspect;

import java.util.*;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.util.Annotations;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;

/**
 * Default {@link BeanDescription} implementation used by Jackson.
 *<p>
 * Although sub-classing is a theoretical possibility there are no known
 * use cases for that, nor is such usage tested or supported.
 * Separation from API is mostly to isolate some implementation details
 * here and keep API simple.
 */
public class BasicBeanDescription extends BeanDescription
{
    // since 2.9
    private final static Class<?>[] NO_VIEWS = new Class<?>[0];

    /*
    /**********************************************************
    /* General configuration
    /**********************************************************
     */

    /**
     * We will hold a reference to the collector in cases where
     * information is lazily accessed and constructed; properties
     * are only accessed when they are actually needed.
     */
    final protected POJOPropertiesCollector _propCollector;

    final protected MapperConfig<?> _config;

    final protected AnnotationIntrospector _annotationIntrospector;

    /*
    /**********************************************************
    /* Information about type itself
    /**********************************************************
     */

    /**
     * Information collected about the class introspected.
     */
    final protected AnnotatedClass _classInfo;

    /**
     * @since 2.9
     */
    protected Class<?>[] _defaultViews;

    /**
     * @since 2.9
     */
    protected boolean _defaultViewsResolved;

    /*
    /**********************************************************
    /* Member information
    /**********************************************************
     */

    /**
     * Properties collected for the POJO; initialized as needed.
     */
    protected List<BeanPropertyDefinition> _properties;

    /**
     * Details of Object Id to include, if any
     */
    protected ObjectIdInfo _objectIdInfo;

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

    protected BasicBeanDescription(POJOPropertiesCollector coll,
            JavaType type, AnnotatedClass classDef)
    {
        super(type);
        _propCollector = coll;
        _config = coll.getConfig();
        // NOTE: null config only for some pre-constructed types
        if (_config == null) {
            _annotationIntrospector = null;
        } else {
            _annotationIntrospector = _config.getAnnotationIntrospector();
        }
        _classInfo = classDef;
    }

    /**
     * Alternate constructor used in cases where property information is not needed,
     * only class info.
     */
    protected BasicBeanDescription(MapperConfig<?> config,
            JavaType type, AnnotatedClass classDef, List<BeanPropertyDefinition> props)
    {
        super(type);
        _propCollector = null;
        _config = config;
        // NOTE: null config only for some pre-constructed types
        if (_config == null) {
            _annotationIntrospector = null;
        } else {
            _annotationIntrospector = _config.getAnnotationIntrospector();
        }
        _classInfo = classDef;
        _properties = props;
    }

    protected BasicBeanDescription(POJOPropertiesCollector coll)
    {
        this(coll, coll.getType(), coll.getClassDef());
        _objectIdInfo = coll.getObjectIdInfo();
    }

    /**
     * Factory method to use for constructing an instance to use for building
     * deserializers.
     */
    public static BasicBeanDescription forDeserialization(POJOPropertiesCollector coll) {
        return new BasicBeanDescription(coll);
    }

    /**
     * Factory method to use for constructing an instance to use for building
     * serializers.
     */
    public static BasicBeanDescription forSerialization(POJOPropertiesCollector coll) {
        return new BasicBeanDescription(coll);
    }

    /**
     * Factory method to use for constructing an instance to use for purposes
     * other than building serializers or deserializers; will only have information
     * on class, not on properties.
     */
    public static BasicBeanDescription forOtherUse(MapperConfig<?> config,
            JavaType type, AnnotatedClass ac)
    {
        return new BasicBeanDescription(config, type,
                ac, Collections.<BeanPropertyDefinition>emptyList());
    }

    protected List<BeanPropertyDefinition> _properties() {
        if (_properties == null) {
            _properties = _propCollector.getProperties();
        }
        return _properties;
    }

    /*
    /**********************************************************
    /* Limited modifications by core databind functionality
    /**********************************************************
     */

    /**
     * Method that can be used to prune unwanted properties, during
     * construction of serializers and deserializers.
     * Use with utmost care, if at all...
     *
     * @since 2.1
     */
    public boolean removeProperty(String propName)
    {
        Iterator<BeanPropertyDefinition> it = _properties().iterator();
        while (it.hasNext()) {
            BeanPropertyDefinition prop = it.next();
            if (prop.getName().equals(propName)) {
                it.remove();
                return true;
            }
        }
        return false;
    }

    public boolean addProperty(BeanPropertyDefinition def)
    {
        // first: ensure we do not have such property
        if (hasProperty(def.getFullName())) {
            return false;
        }
        _properties().add(def);
        return true;
    }

    /**
     * @since 2.6
     */
    public boolean hasProperty(PropertyName name) {
        return findProperty(name) != null;
    }

    /**
     * @since 2.6
     */
    public BeanPropertyDefinition findProperty(PropertyName name)
    {
        for (BeanPropertyDefinition prop : _properties()) {
            if (prop.hasName(name)) {
                return prop;
            }
        }
        return null;
    }

    /*
    /**********************************************************
    /* Simple accessors from BeanDescription
    /**********************************************************
     */

    @Override
    public AnnotatedClass getClassInfo() { return _classInfo; }

    @Override
    public ObjectIdInfo getObjectIdInfo() { return  _objectIdInfo; }

    @Override
    public List<BeanPropertyDefinition> findProperties() {
        return _properties();
    }

    @Override // since 2.12
    public AnnotatedMember findJsonKeyAccessor() {
        return (_propCollector == null) ? null
                : _propCollector.getJsonKeyAccessor();
    }

    @Override // since 2.9
    public AnnotatedMember findJsonValueAccessor() {
        return (_propCollector == null) ? null
                : _propCollector.getJsonValueAccessor();
    }

    @Override
    public Set<String> getIgnoredPropertyNames() {
        Set<String> ign = (_propCollector == null) ? null
                : _propCollector.getIgnoredPropertyNames();
        if (ign == null) {
            return Collections.emptySet();
        }
        return ign;
    }

    @Override
    public boolean hasKnownClassAnnotations() {
        return _classInfo.hasAnnotations();
    }

    @Override
    public Annotations getClassAnnotations() {
        return _classInfo.getAnnotations();
    }

    @Override
    public AnnotatedConstructor findDefaultConstructor() {
        return _classInfo.getDefaultConstructor();
    }

    @Override
    public AnnotatedMember findAnySetterAccessor() throws IllegalArgumentException
    {
        if (_propCollector != null) {
            AnnotatedMethod anyMethod = _propCollector.getAnySetterMethod();
            if (anyMethod != null) {
                // Also, let's be somewhat strict on how field name is to be
                // passed; String, Object make sense, others not so much.

                /* !!! 18-May-2009, tatu: how about enums? Can add support if
                 *  requested; easy enough for devs to add support within method.
                 */
                Class<?> type = anyMethod.getRawParameterType(0);
                if ((type != String.class) && (type != Object.class)) {
                    throw new IllegalArgumentException(String.format(
"Invalid 'any-setter' annotation on method '%s()': first argument not of type String or Object, but %s",
anyMethod.getName(), type.getName()));
                }
                return anyMethod;
            }
            AnnotatedMember anyField = _propCollector.getAnySetterField();
            if (anyField != null) {
                // For now let's require a Map; in future can add support for other
                // types like perhaps Iterable<Map.Entry>?
                Class<?> type = anyField.getRawType();
                if (!Map.class.isAssignableFrom(type)
                        && !JsonNode.class.isAssignableFrom(type)) {
                    throw new IllegalArgumentException(String.format(
"Invalid 'any-setter' annotation on field '%s': type is not instance of `java.util.Map` or `JsonNode`",
anyField.getName()));
                }
                return anyField;
            }
        }
        return null;
    }

    @Override
    public Map<Object, AnnotatedMember> findInjectables() {
        if (_propCollector != null) {
            return _propCollector.getInjectables();
        }
        return Collections.emptyMap();
    }

    @Override
    public List<AnnotatedConstructor> getConstructors() {
        return _classInfo.getConstructors();
    }

    @Override
    public List<AnnotatedAndMetadata<AnnotatedConstructor, JsonCreator.Mode>> getConstructorsWithMode() {
        List<AnnotatedConstructor> allCtors = _classInfo.getConstructors();
        if (allCtors.isEmpty()) {
            return Collections.emptyList();
        }
        List<AnnotatedAndMetadata<AnnotatedConstructor, JsonCreator.Mode>> result = new ArrayList<>();
        for (AnnotatedConstructor ctor : allCtors) {
            JsonCreator.Mode mode = _annotationIntrospector.findCreatorAnnotation(_config, ctor);
            if (mode == JsonCreator.Mode.DISABLED) {
                continue;
            }
            result.add(AnnotatedAndMetadata.of(ctor, mode));
        }
        return result;
    }

    @Override
    public PotentialCreators getPotentialCreators() {
        if (_propCollector == null) {
            return new PotentialCreators();
        }
        return _propCollector.getPotentialCreators();
    }
    
    @Override
    public Object instantiateBean(boolean fixAccess) {
        AnnotatedConstructor ac = _classInfo.getDefaultConstructor();
        if (ac == null) {
            return null;
        }
        if (fixAccess) {
            ac.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
        }
        try {
            return ac.call();
        } catch (Exception e) {
            Throwable t = e;
            while (t.getCause() != null) {
                t = t.getCause();
            }
            ClassUtil.throwIfError(t);
            ClassUtil.throwIfRTE(t);
            throw new IllegalArgumentException("Failed to instantiate bean of type "
                    +_classInfo.getAnnotated().getName()+": ("+t.getClass().getName()+") "
                    +ClassUtil.exceptionMessage(t), t);
        }
    }

    /*
    /**********************************************************
    /* Simple accessors, extended
    /**********************************************************
     */

    @Override
    public AnnotatedMethod findMethod(String name, Class<?>[] paramTypes) {
        return _classInfo.findMethod(name, paramTypes);
    }

    /*
    /**********************************************************
    /* General per-class annotation introspection
    /**********************************************************
     */

    @Override // since 2.17
    public JsonFormat.Value findExpectedFormat()
    {
        if (_propCollector == null) {
            return JsonFormat.Value.empty();
        }
        return _propCollector.getFormatOverrides();
    }

    @Override // since 2.9
    public Class<?>[] findDefaultViews()
    {
        if (!_defaultViewsResolved) {
            _defaultViewsResolved = true;
            Class<?>[] def = (_annotationIntrospector == null) ? null
                    : _annotationIntrospector.findViews(_classInfo);
            // one more twist: if default inclusion disabled, need to force empty set of views
            if (def == null) {
                if (!_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
                    def = NO_VIEWS;
                }
            }
            _defaultViews = def;
        }
        return _defaultViews;
    }

    /*
    /**********************************************************
    /* Introspection for serialization
    /**********************************************************
     */

    @Override
    public Converter<Object,Object> findSerializationConverter()
    {
        if (_annotationIntrospector == null) {
            return null;
        }
        return _createConverter(_annotationIntrospector.findSerializationConverter(_classInfo));
    }

    /**
     * Method for determining whether null properties should be written
     * out for a Bean of introspected type. This is based on global
     * feature (lowest priority, passed as argument)
     * and per-class annotation (highest priority).
     */
    @Override
    public JsonInclude.Value findPropertyInclusion(JsonInclude.Value defValue) {
        if (_annotationIntrospector != null) {
            JsonInclude.Value incl = _annotationIntrospector.findPropertyInclusion(_classInfo);
            if (incl != null) {
                return (defValue == null) ? incl : defValue.withOverrides(incl);
            }
        }
        return defValue;
    }

    /**
     * Method used to locate the method of introspected class that
     * implements {@link com.fasterxml.jackson.annotation.JsonAnyGetter}.
     * If no such method exists null is returned.
     * If more than one are found, an exception is thrown.
     */
    @Override
    public AnnotatedMember findAnyGetter() throws IllegalArgumentException
    {
        if (_propCollector != null) {
            AnnotatedMember anyGetter = _propCollector.getAnyGetterMethod();
            if (anyGetter != null) {
                // For now let's require a Map; in future can add support for other
                // types like perhaps Iterable<Map.Entry>?
                Class<?> type = anyGetter.getRawType();
                if (!Map.class.isAssignableFrom(type)) {
                    throw new IllegalArgumentException(String.format(
                            "Invalid 'any-getter' annotation on method %s(): return type is not instance of java.util.Map",
                            anyGetter.getName()));
                }
                return anyGetter;
            }

            AnnotatedMember anyField = _propCollector.getAnyGetterField();
            if (anyField != null) {
                // For now let's require a Map; in future can add support for other
                // types like perhaps Iterable<Map.Entry>?
                Class<?> type = anyField.getRawType();
                if (!Map.class.isAssignableFrom(type)) {
                    throw new IllegalArgumentException(String.format(
                            "Invalid 'any-getter' annotation on field '%s': type is not instance of java.util.Map",
                            anyField.getName()));
                }
                return anyField;
            }
        }
        return null;
    }

    @Override
    public List<BeanPropertyDefinition> findBackReferences()
    {
        List<BeanPropertyDefinition> result = null;
        HashSet<String> names = null;
        for (BeanPropertyDefinition property : _properties()) {
            AnnotationIntrospector.ReferenceProperty refDef = property.findReferenceType();
            if ((refDef == null) || !refDef.isBackReference()) {
                continue;
            }
            final String refName = refDef.getName();
            if (result == null) {
                result = new ArrayList<BeanPropertyDefinition>();
                names = new HashSet<>();
                names.add(refName);
            } else {
                if (!names.add(refName)) {
                    throw new IllegalArgumentException("Multiple back-reference properties with name "+ClassUtil.name(refName));
                }
            }
            result.add(property);
        }
        return result;
    }

    /*
    /**********************************************************
    /* Introspection for deserialization, factories
    /**********************************************************
     */

    @Override
    public List<AnnotatedMethod> getFactoryMethods()
    {
        // must filter out anything that clearly is not a factory method
        List<AnnotatedMethod> candidates = _classInfo.getFactoryMethods();
        if (candidates.isEmpty()) {
            return candidates;
        }
        List<AnnotatedMethod> result = null;
        for (AnnotatedMethod am : candidates) {
            if (isFactoryMethod(am)) {
                if (result == null) {
                    result = new ArrayList<AnnotatedMethod>();
                }
                result.add(am);
            }
        }
        if (result == null) {
            return Collections.emptyList();
        }
        return result;
    }

    @Override // since 2.13
    public List<AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode>> getFactoryMethodsWithMode()
    {
        List<AnnotatedMethod> candidates = _classInfo.getFactoryMethods();
        if (candidates.isEmpty()) {
            return Collections.emptyList();
        }
        List<AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode>> result = null;
        for (AnnotatedMethod am : candidates) {
            AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode> match
                = findFactoryMethodMetadata(am);
            if (match != null) {
                if (result == null) {
                    result = new ArrayList<>();
                }
                result.add(match);
            }
        }
        if (result == null) {
            return Collections.emptyList();
        }
        return result;
    }

    protected boolean isFactoryMethod(AnnotatedMethod am)
    {
        // First: return type must be compatible with the introspected class
        // (i.e. allowed to be sub-class, although usually is the same class)
        Class<?> rt = am.getRawReturnType();
        if (!getBeanClass().isAssignableFrom(rt)) {
            return false;
        }
        /* Also: must be a recognized factory method, meaning:
         * (a) marked with @JsonCreator annotation, or
         * (b) 1-argument "valueOf" (at this point, need not be public), or
         * (c) 1-argument "fromString()" AND takes {@code String} as the argument
         */
        JsonCreator.Mode mode = _annotationIntrospector.findCreatorAnnotation(_config, am);
        if ((mode != null) && (mode != JsonCreator.Mode.DISABLED)) {
            return true;
        }
        final String name = am.getName();
        // 24-Oct-2016, tatu: As per [databind#1429] must ensure takes exactly one arg
        if ("valueOf".equals(name)) {
            if (am.getParameterCount() == 1) {
                return true;
            }
        }
        // [databind#208] Also accept "fromString()", if takes String or CharSequence
        if ("fromString".equals(name)) {
            if (am.getParameterCount() == 1) {
                Class<?> cls = am.getRawParameterType(0);
                if (cls == String.class || CharSequence.class.isAssignableFrom(cls)) {
                    return true;
                }
            }
        }
        return false;
    }

    // @since 2.13
    protected AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode> findFactoryMethodMetadata(AnnotatedMethod am)
    {
        // First: return type must be compatible with the introspected class
        // (i.e. allowed to be sub-class, although usually is the same class)
        Class<?> rt = am.getRawReturnType();
        if (!getBeanClass().isAssignableFrom(rt)) {
            return null;
        }
        // Also: must be a recognized factory method, meaning:
        // (a) marked with @JsonCreator annotation, or
        // (b) 1-argument "valueOf" (at this point, need not be public), or
        // (c) 1-argument "fromString()" AND takes {@code String} as the argument
        JsonCreator.Mode mode = _annotationIntrospector.findCreatorAnnotation(_config, am);
        if (mode != null) {
            if (mode == JsonCreator.Mode.DISABLED) {
                return null;
            }
            return AnnotatedAndMetadata.of(am, mode);
        }
        final String name = am.getName();
        // 24-Oct-2016, tatu: As per [databind#1429] must ensure takes exactly one arg
        if ("valueOf".equals(name)) {
            if (am.getParameterCount() == 1) {
                return AnnotatedAndMetadata.of(am, mode);
            }
        }
        // [databind#208] Also accept "fromString()", if takes String or CharSequence
        if ("fromString".equals(name)) {
            if (am.getParameterCount() == 1) {
                Class<?> cls = am.getRawParameterType(0);
                if (cls == String.class || CharSequence.class.isAssignableFrom(cls)) {
                    return AnnotatedAndMetadata.of(am, mode);
                }
            }
        }
        return null;
    }

    /*
    /**********************************************************
    /* Introspection for deserialization, other
    /**********************************************************
     */

    @Override
    public Class<?> findPOJOBuilder() {
        return (_annotationIntrospector == null) ?
    			null : _annotationIntrospector.findPOJOBuilder(_classInfo);
    }

    @Override
    public JsonPOJOBuilder.Value findPOJOBuilderConfig()
    {
        return (_annotationIntrospector == null) ?
                null : _annotationIntrospector.findPOJOBuilderConfig(_classInfo);
    }

    @Override
    public Converter<Object,Object> findDeserializationConverter()
    {
        if (_annotationIntrospector == null) {
            return null;
        }
        return _createConverter(_annotationIntrospector.findDeserializationConverter(_classInfo));
    }

    @Override
    public String findClassDescription() {
        return (_annotationIntrospector == null) ?
                null : _annotationIntrospector.findClassDescription(_classInfo);
    }

    /*
    /**********************************************************
    /* Helper methods, other
    /**********************************************************
     */

    @SuppressWarnings("unchecked")
    protected Converter<Object,Object> _createConverter(Object converterDef)
    {
        if (converterDef == null) {
            return null;
        }
        if (converterDef instanceof Converter<?,?>) {
            return (Converter<Object,Object>) converterDef;
        }
        if (!(converterDef instanceof Class)) {
            throw new IllegalStateException("AnnotationIntrospector returned Converter definition of type "
                    +converterDef.getClass().getName()+"; expected type Converter or Class<Converter> instead");
        }
        Class<?> converterClass = (Class<?>)converterDef;
        // there are some known "no class" markers to consider too:
        if (converterClass == Converter.None.class || ClassUtil.isBogusClass(converterClass)) {
            return null;
        }
        if (!Converter.class.isAssignableFrom(converterClass)) {
            throw new IllegalStateException("AnnotationIntrospector returned Class "
                    +converterClass.getName()+"; expected Class<Converter>");
        }
        HandlerInstantiator hi = _config.getHandlerInstantiator();
        Converter<?,?> conv = (hi == null) ? null : hi.converterInstance(_config, _classInfo, converterClass);
        if (conv == null) {
            conv = (Converter<?,?>) ClassUtil.createInstance(converterClass,
                    _config.canOverrideAccessModifiers());
        }
        return (Converter<Object,Object>) conv;
    }
}