| package com.fasterxml.jackson.databind.deser; |
| |
| import java.util.*; |
| |
| import com.fasterxml.jackson.databind.*; |
| import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; |
| import com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap; |
| import com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty; |
| import com.fasterxml.jackson.databind.deser.impl.ObjectIdReader; |
| import com.fasterxml.jackson.databind.deser.impl.ValueInjector; |
| import com.fasterxml.jackson.databind.introspect.*; |
| import com.fasterxml.jackson.databind.util.Annotations; |
| |
| /** |
| * Builder class used for aggregating deserialization information about |
| * a POJO, in order to build a {@link JsonDeserializer} for deserializing |
| * instances. |
| */ |
| public class BeanDeserializerBuilder |
| { |
| /* |
| /********************************************************** |
| /* Configuration |
| /********************************************************** |
| */ |
| |
| final protected DeserializationConfig _config; |
| |
| /** |
| * @since 2.9 |
| */ |
| final protected DeserializationContext _context; |
| |
| /* |
| /********************************************************** |
| /* General information about POJO |
| /********************************************************** |
| */ |
| |
| /** |
| * Introspected information about POJO for deserializer to handle |
| */ |
| final protected BeanDescription _beanDesc; |
| |
| /* |
| /********************************************************** |
| /* Accumulated information about properties |
| /********************************************************** |
| */ |
| |
| /** |
| * Properties to deserialize collected so far. |
| */ |
| final protected Map<String, SettableBeanProperty> _properties |
| = new LinkedHashMap<String, SettableBeanProperty>(); |
| |
| /** |
| * Value injectors for deserialization |
| */ |
| protected List<ValueInjector> _injectables; |
| |
| /** |
| * Back-reference properties this bean contains (if any) |
| */ |
| protected HashMap<String, SettableBeanProperty> _backRefProperties; |
| |
| /** |
| * Set of names of properties that are recognized but are to be ignored for deserialization |
| * purposes (meaning no exception is thrown, value is just skipped). |
| */ |
| protected HashSet<String> _ignorableProps; |
| |
| /** |
| * Object that will handle value instantiation for the bean type. |
| */ |
| protected ValueInstantiator _valueInstantiator; |
| |
| /** |
| * Handler for Object Id values, if Object Ids are enabled for the |
| * bean type. |
| */ |
| protected ObjectIdReader _objectIdReader; |
| |
| /** |
| * Fallback setter used for handling any properties that are not |
| * mapped to regular setters. If setter is not null, it will be |
| * called once for each such property. |
| */ |
| protected SettableAnyProperty _anySetter; |
| |
| /** |
| * Flag that can be set to ignore and skip unknown properties. |
| * If set, will not throw an exception for unknown properties. |
| */ |
| protected boolean _ignoreAllUnknown; |
| |
| /** |
| * When creating Builder-based deserializers, this indicates |
| * method to call on builder to finalize value. |
| */ |
| protected AnnotatedMethod _buildMethod; |
| |
| /** |
| * In addition, Builder may have additional configuration |
| */ |
| protected JsonPOJOBuilder.Value _builderConfig; |
| |
| /* |
| /********************************************************** |
| /* Life-cycle: construction |
| /********************************************************** |
| */ |
| |
| public BeanDeserializerBuilder(BeanDescription beanDesc, |
| DeserializationContext ctxt) |
| { |
| _beanDesc = beanDesc; |
| _context = ctxt; |
| _config = ctxt.getConfig(); |
| } |
| |
| /** |
| * Copy constructor for sub-classes to use, when constructing |
| * custom builder instances |
| */ |
| protected BeanDeserializerBuilder(BeanDeserializerBuilder src) |
| { |
| _beanDesc = src._beanDesc; |
| _context = src._context; |
| _config = src._config; |
| |
| // let's make copy of properties |
| _properties.putAll(src._properties); |
| _injectables = _copy(src._injectables); |
| _backRefProperties = _copy(src._backRefProperties); |
| // Hmmh. Should we create defensive copies here? For now, not yet |
| _ignorableProps = src._ignorableProps; |
| _valueInstantiator = src._valueInstantiator; |
| _objectIdReader = src._objectIdReader; |
| |
| _anySetter = src._anySetter; |
| _ignoreAllUnknown = src._ignoreAllUnknown; |
| |
| _buildMethod = src._buildMethod; |
| _builderConfig = src._builderConfig; |
| } |
| |
| private static HashMap<String, SettableBeanProperty> _copy(HashMap<String, SettableBeanProperty> src) { |
| return (src == null) ? null |
| : new HashMap<String, SettableBeanProperty>(src); |
| } |
| |
| private static <T> List<T> _copy(List<T> src) { |
| return (src == null) ? null : new ArrayList<T>(src); |
| } |
| |
| /* |
| /********************************************************** |
| /* Life-cycle: state modification (adders, setters) |
| /********************************************************** |
| */ |
| |
| /** |
| * Method for adding a new property or replacing a property. |
| */ |
| public void addOrReplaceProperty(SettableBeanProperty prop, boolean allowOverride) { |
| _properties.put(prop.getName(), prop); |
| } |
| |
| /** |
| * Method to add a property setter. Will ensure that there is no |
| * unexpected override; if one is found will throw a |
| * {@link IllegalArgumentException}. |
| */ |
| public void addProperty(SettableBeanProperty prop) |
| { |
| SettableBeanProperty old = _properties.put(prop.getName(), prop); |
| if (old != null && old != prop) { // should never occur... |
| throw new IllegalArgumentException("Duplicate property '"+prop.getName()+"' for "+_beanDesc.getType()); |
| } |
| } |
| |
| /** |
| * Method called to add a property that represents so-called back reference; |
| * reference that "points back" to object that has forward reference to |
| * currently built bean. |
| */ |
| public void addBackReferenceProperty(String referenceName, SettableBeanProperty prop) |
| { |
| if (_backRefProperties == null) { |
| _backRefProperties = new HashMap<String, SettableBeanProperty>(4); |
| } |
| // 15-Sep-2016, tatu: For some reason fixing access at point of `build()` does |
| // NOT work (2 failing unit tests). Not 100% clear why, but for now force |
| // access set early; unfortunate, but since it works.... |
| prop.fixAccess(_config); |
| _backRefProperties.put(referenceName, prop); |
| // 16-Jan-2018, tatu: As per [databind#1878] we may want to leave it as is, to allow |
| // population for cases of "wrong direction", traversing parent first |
| // If this causes problems should probably instead include in "ignored properties" list |
| // Alternatively could also extend annotation to allow/disallow explicit value from input |
| /* |
| if (_properties != null) { |
| _properties.remove(prop.getName()); |
| } |
| */ |
| } |
| |
| public void addInjectable(PropertyName propName, JavaType propType, |
| Annotations contextAnnotations, AnnotatedMember member, |
| Object valueId) |
| { |
| if (_injectables == null) { |
| _injectables = new ArrayList<ValueInjector>(); |
| } |
| boolean fixAccess = _config.canOverrideAccessModifiers(); |
| boolean forceAccess = fixAccess && _config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS); |
| if (fixAccess) { |
| member.fixAccess(forceAccess); |
| } |
| _injectables.add(new ValueInjector(propName, propType, member, valueId)); |
| } |
| |
| /** |
| * Method that will add property name as one of properties that can |
| * be ignored if not recognized. |
| */ |
| public void addIgnorable(String propName) |
| { |
| if (_ignorableProps == null) { |
| _ignorableProps = new HashSet<String>(); |
| } |
| _ignorableProps.add(propName); |
| } |
| |
| /** |
| * Method called by deserializer factory, when a "creator property" |
| * (something that is passed via constructor- or factory method argument; |
| * instead of setter or field). |
| *<p> |
| * Default implementation does not do anything; we may need to revisit this |
| * decision if these properties need to be available through accessors. |
| * For now, however, we just have to ensure that we don't try to resolve |
| * types that masked setter/field has (see [JACKSON-700] for details). |
| */ |
| public void addCreatorProperty(SettableBeanProperty prop) |
| { |
| addProperty(prop); |
| } |
| |
| public void setAnySetter(SettableAnyProperty s) |
| { |
| if (_anySetter != null && s != null) { |
| throw new IllegalStateException("_anySetter already set to non-null"); |
| } |
| _anySetter = s; |
| } |
| |
| public void setIgnoreUnknownProperties(boolean ignore) { |
| _ignoreAllUnknown = ignore; |
| } |
| |
| public void setValueInstantiator(ValueInstantiator inst) { |
| _valueInstantiator = inst; |
| } |
| |
| public void setObjectIdReader(ObjectIdReader r) { |
| _objectIdReader = r; |
| } |
| |
| public void setPOJOBuilder(AnnotatedMethod buildMethod, JsonPOJOBuilder.Value config) { |
| _buildMethod = buildMethod; |
| _builderConfig = config; |
| } |
| |
| /* |
| /********************************************************** |
| /* Public accessors |
| /********************************************************** |
| */ |
| |
| /** |
| * Method that allows accessing all properties that this |
| * builder currently contains. |
| *<p> |
| * Note that properties are returned in order that properties |
| * are ordered (explictly, or by rule), which is the serialization |
| * order. |
| */ |
| public Iterator<SettableBeanProperty> getProperties() { |
| return _properties.values().iterator(); |
| } |
| |
| public SettableBeanProperty findProperty(PropertyName propertyName) { |
| return _properties.get(propertyName.getSimpleName()); |
| } |
| |
| public boolean hasProperty(PropertyName propertyName) { |
| return findProperty(propertyName) != null; |
| } |
| |
| public SettableBeanProperty removeProperty(PropertyName name) { |
| return _properties.remove(name.getSimpleName()); |
| } |
| |
| public SettableAnyProperty getAnySetter() { |
| return _anySetter; |
| } |
| |
| public ValueInstantiator getValueInstantiator() { |
| return _valueInstantiator; |
| } |
| |
| public List<ValueInjector> getInjectables() { |
| return _injectables; |
| } |
| |
| public ObjectIdReader getObjectIdReader() { |
| return _objectIdReader; |
| } |
| |
| public AnnotatedMethod getBuildMethod() { |
| return _buildMethod; |
| } |
| |
| public JsonPOJOBuilder.Value getBuilderConfig() { |
| return _builderConfig; |
| } |
| |
| /** |
| * @since 2.9.4 |
| */ |
| public boolean hasIgnorable(String name) { |
| return (_ignorableProps != null) && _ignorableProps.contains(name); |
| } |
| |
| /* |
| /********************************************************** |
| /* Build method(s) |
| /********************************************************** |
| */ |
| |
| /** |
| * Method for constructing a {@link BeanDeserializer}, given all |
| * information collected. |
| */ |
| public JsonDeserializer<?> build() |
| { |
| Collection<SettableBeanProperty> props = _properties.values(); |
| _fixAccess(props); |
| BeanPropertyMap propertyMap = BeanPropertyMap.construct(props, |
| _config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES), |
| _collectAliases(props)); |
| propertyMap.assignIndexes(); |
| |
| // view processing must be enabled if: |
| // (a) fields are not included by default (when deserializing with view), OR |
| // (b) one of properties has view(s) to included in defined |
| boolean anyViews = !_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION); |
| if (!anyViews) { |
| for (SettableBeanProperty prop : props) { |
| if (prop.hasViews()) { |
| anyViews = true; |
| break; |
| } |
| } |
| } |
| |
| // one more thing: may need to create virtual ObjectId property: |
| if (_objectIdReader != null) { |
| /* 18-Nov-2012, tatu: May or may not have annotations for id property; |
| * but no easy access. But hard to see id property being optional, |
| * so let's consider required at this point. |
| */ |
| ObjectIdValueProperty prop = new ObjectIdValueProperty(_objectIdReader, PropertyMetadata.STD_REQUIRED); |
| propertyMap = propertyMap.withProperty(prop); |
| } |
| |
| return new BeanDeserializer(this, |
| _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, |
| anyViews); |
| } |
| |
| /** |
| * Alternate build method used when we must be using some form of |
| * abstract resolution, usually by using addition Type Id |
| * ("polymorphic deserialization") |
| * |
| * @since 2.0 |
| */ |
| public AbstractDeserializer buildAbstract() { |
| return new AbstractDeserializer(this, _beanDesc, _backRefProperties, _properties); |
| } |
| |
| /** |
| * Method for constructing a specialized deserializer that uses |
| * additional external Builder object during data binding. |
| */ |
| public JsonDeserializer<?> buildBuilderBased(JavaType valueType, String expBuildMethodName) |
| throws JsonMappingException |
| { |
| // First: validation; must have build method that returns compatible type |
| if (_buildMethod == null) { |
| // as per [databind#777], allow empty name |
| if (!expBuildMethodName.isEmpty()) { |
| _context.reportBadDefinition(_beanDesc.getType(), |
| String.format("Builder class %s does not have build method (name: '%s')", |
| _beanDesc.getBeanClass().getName(), |
| expBuildMethodName)); |
| } |
| } else { |
| // also: type of the method must be compatible |
| Class<?> rawBuildType = _buildMethod.getRawReturnType(); |
| Class<?> rawValueType = valueType.getRawClass(); |
| if ((rawBuildType != rawValueType) |
| && !rawBuildType.isAssignableFrom(rawValueType) |
| && !rawValueType.isAssignableFrom(rawBuildType)) { |
| _context.reportBadDefinition(_beanDesc.getType(), |
| String.format("Build method '%s' has wrong return type (%s), not compatible with POJO type (%s)", |
| _buildMethod.getFullName(), |
| rawBuildType.getName(), |
| valueType.getRawClass().getName())); |
| } |
| } |
| // And if so, we can try building the deserializer |
| Collection<SettableBeanProperty> props = _properties.values(); |
| _fixAccess(props); |
| BeanPropertyMap propertyMap = BeanPropertyMap.construct(props, |
| _config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES), |
| _collectAliases(props)); |
| propertyMap.assignIndexes(); |
| |
| boolean anyViews = !_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION); |
| |
| if (!anyViews) { |
| for (SettableBeanProperty prop : props) { |
| if (prop.hasViews()) { |
| anyViews = true; |
| break; |
| } |
| } |
| } |
| |
| if (_objectIdReader != null) { |
| // May or may not have annotations for id property; but no easy access. |
| // But hard to see id property being optional, so let's consider required at this point. |
| ObjectIdValueProperty prop = new ObjectIdValueProperty(_objectIdReader, |
| PropertyMetadata.STD_REQUIRED); |
| propertyMap = propertyMap.withProperty(prop); |
| } |
| |
| return createBuilderBasedDeserializer(valueType, propertyMap, anyViews); |
| } |
| |
| /** |
| * Extension point for overriding the actual creation of the builder deserializer. |
| * |
| * @since 2.10.1 (officially in 2.10.0) |
| */ |
| protected JsonDeserializer<?> createBuilderBasedDeserializer(JavaType valueType, |
| BeanPropertyMap propertyMap, boolean anyViews) { |
| return new BuilderBasedDeserializer(this, |
| _beanDesc, valueType, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, |
| anyViews); |
| } |
| |
| /* |
| /********************************************************** |
| /* Internal helper method(s) |
| /********************************************************** |
| */ |
| |
| protected void _fixAccess(Collection<SettableBeanProperty> mainProps) |
| { |
| /* 07-Sep-2016, tatu: Ideally we should be able to avoid forcing |
| * access to properties that are likely ignored, but due to |
| * renaming it seems this is not a safe thing to do (there was |
| * at least one failing test). May need to dig deeper in future; |
| * for now let's just play it safe. |
| */ |
| /* |
| Set<String> ignorable = _ignorableProps; |
| if (ignorable == null) { |
| ignorable = Collections.emptySet(); |
| } |
| */ |
| for (SettableBeanProperty prop : mainProps) { |
| /* |
| // first: no point forcing access on to-be-ignored properties |
| if (ignorable.contains(prop.getName())) { |
| continue; |
| } |
| */ |
| prop.fixAccess(_config); |
| } |
| // 15-Sep-2016, tatu: Access via back-ref properties has been done earlier |
| // as it has to, for some reason, so not repeated here. |
| /* |
| if (_backRefProperties != null) { |
| for (SettableBeanProperty prop : _backRefProperties.values()) { |
| prop.fixAccess(_config); |
| } |
| } |
| */ |
| if (_anySetter != null) { |
| _anySetter.fixAccess(_config); |
| } |
| if (_buildMethod != null) { |
| _buildMethod.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); |
| } |
| } |
| |
| protected Map<String,List<PropertyName>> _collectAliases(Collection<SettableBeanProperty> props) |
| { |
| Map<String,List<PropertyName>> mapping = null; |
| AnnotationIntrospector intr = _config.getAnnotationIntrospector(); |
| if (intr != null) { |
| for (SettableBeanProperty prop : props) { |
| List<PropertyName> aliases = intr.findPropertyAliases(prop.getMember()); |
| if ((aliases == null) || aliases.isEmpty()) { |
| continue; |
| } |
| if (mapping == null) { |
| mapping = new HashMap<>(); |
| } |
| mapping.put(prop.getName(), aliases); |
| } |
| } |
| if (mapping == null) { |
| return Collections.emptyMap(); |
| } |
| return mapping; |
| } |
| } |