XmlValueInstantiators.java
package tools.jackson.dataformat.xml.deser;
import java.util.*;
import tools.jackson.databind.*;
import tools.jackson.databind.deser.SettableBeanProperty;
import tools.jackson.databind.deser.ValueInstantiator;
import tools.jackson.databind.deser.ValueInstantiators;
import tools.jackson.databind.introspect.AnnotatedMember;
import tools.jackson.databind.introspect.BeanPropertyDefinition;
import tools.jackson.dataformat.xml.annotation.JacksonXmlText;
import tools.jackson.dataformat.xml.util.AnnotationUtil;
/**
* {@link ValueInstantiators} implementation that renames creator properties
* to match the names that {@link XmlBeanDeserializerModifier#updateProperties}
* will give to the corresponding property definitions.
*<p>
* This is needed because {@code updateProperties()} renames property definitions
* (e.g., {@code @JacksonXmlText} properties to {@code ""}, wrapped collections to
* their wrapper name), but creator parameters retain their original names. Without
* this renaming, {@code BeanDeserializerFactory.addBeanProps()} cannot link property
* definitions to creator parameters.
*
* @since 3.2
*/
public class XmlValueInstantiators
extends ValueInstantiators.Base
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
protected final String _cfgNameForTextValue;
public XmlValueInstantiators(String nameForTextValue)
{
_cfgNameForTextValue = nameForTextValue;
}
static class XmlDelegatingInstantiator extends ValueInstantiator.Delegating {
private static final long serialVersionUID = 1L;
private final SettableBeanProperty[] _renamedCreatorProps;
public XmlDelegatingInstantiator(ValueInstantiator delegate, SettableBeanProperty[] renamedCreatorProps) {
super(delegate);
this._renamedCreatorProps = renamedCreatorProps;
}
@Override
public SettableBeanProperty[] getFromObjectArguments(DeserializationConfig config) {
return _renamedCreatorProps;
}
}
@Override
public ValueInstantiator modifyValueInstantiator(DeserializationConfig config,
BeanDescription.Supplier beanDescRef, ValueInstantiator defaultInstantiator)
{
SettableBeanProperty[] creatorProps = defaultInstantiator.getFromObjectArguments(config);
if (creatorProps == null || creatorProps.length == 0) {
return defaultInstantiator;
}
// Build a map of original-property-name -> new-name for renames that
// updateProperties() will perform on the property definitions.
// We need to apply the same renames to creator properties so names match.
Map<String, String> renames = _findPropertyRenames(config, beanDescRef);
if (renames.isEmpty()) {
// Also check for @JacksonXmlText directly on creator parameters
// (may not appear in bean property definitions for constructor-only classes)
if (!_hasXmlTextCreatorParam(creatorProps)) {
return defaultInstantiator;
}
}
boolean hasRenames = false;
SettableBeanProperty[] renamedCreatorProps = Arrays.copyOf(creatorProps, creatorProps.length);
for (int i = 0, len = renamedCreatorProps.length; i < len; ++i) {
SettableBeanProperty prop = renamedCreatorProps[i];
if (prop == null) {
continue;
}
final String propName = prop.getName();
// First check: direct @JacksonXmlText annotation on the creator parameter
JacksonXmlText textAnn = prop.getAnnotation(JacksonXmlText.class);
if (textAnn != null && textAnn.value()) {
if (!_cfgNameForTextValue.equals(propName)) {
renamedCreatorProps[i] = prop.withSimpleName(_cfgNameForTextValue);
hasRenames = true;
}
continue;
}
// Second check: rename map from property definitions
String newName = renames.get(propName);
if (newName != null && !newName.equals(propName)) {
renamedCreatorProps[i] = prop.withSimpleName(newName);
hasRenames = true;
}
}
return hasRenames ?
new XmlDelegatingInstantiator(defaultInstantiator, renamedCreatorProps)
: defaultInstantiator;
}
private boolean _hasXmlTextCreatorParam(SettableBeanProperty[] creatorProps)
{
for (SettableBeanProperty prop : creatorProps) {
if (prop != null) {
JacksonXmlText textAnn = prop.getAnnotation(JacksonXmlText.class);
if (textAnn != null && textAnn.value()) {
return true;
}
}
}
return false;
}
/**
* Compute the property renames that {@code updateProperties()} will apply,
* by examining the bean's property definitions for {@code @JacksonXmlText}
* and wrapper name annotations.
*
* @return Map of original-property-name to new-property-name
*/
private Map<String, String> _findPropertyRenames(DeserializationConfig config,
BeanDescription.Supplier beanDescRef)
{
final AnnotationIntrospector intr = config.getAnnotationIntrospector();
Map<String, String> renames = Collections.emptyMap();
for (BeanPropertyDefinition propDef : beanDescRef.get().findProperties()) {
final AnnotatedMember member = propDef.getPrimaryMember();
final String origName = propDef.getName();
String renamed = null;
// Check @JacksonXmlText
Boolean isText = AnnotationUtil.findIsTextAnnotation(config, intr, member);
if (Boolean.TRUE.equals(isText)) {
if (!_cfgNameForTextValue.equals(origName)) {
renamed = _cfgNameForTextValue;
}
} else {
// Check wrapper name (for Lists)
PropertyName wrapperName = propDef.getWrapperName();
if (wrapperName != null && wrapperName != PropertyName.NO_NAME) {
String localName = wrapperName.getSimpleName();
if (localName != null && localName.length() > 0
&& !localName.equals(origName)) {
renamed = localName;
}
}
}
if (renamed != null) {
if (renames.isEmpty()) {
renames = new HashMap<>();
}
renames.put(origName, renamed);
}
}
return renames;
}
}