package com.fasterxml.jackson.databind.introspect;

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

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.*;

import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.util.BeanUtil;
import com.fasterxml.jackson.databind.util.ClassUtil;

Helper class used for aggregating information about all possible properties of a POJO.
/** * Helper class used for aggregating information about all possible * properties of a POJO. */
public class POJOPropertiesCollector { /* /********************************************************** /* Configuration /********************************************************** */
Configuration settings
/** * Configuration settings */
protected final MapperConfig<?> _config;
True if introspection is done for serialization (giving precedence for serialization annotations), or not (false, deserialization)
/** * True if introspection is done for serialization (giving * precedence for serialization annotations), or not (false, deserialization) */
protected final boolean _forSerialization;
Since:2.5
/** * @since 2.5 */
protected final boolean _stdBeanNaming;
Type of POJO for which properties are being collected.
/** * Type of POJO for which properties are being collected. */
protected final JavaType _type;
Low-level introspected class information (methods, fields etc)
/** * Low-level introspected class information (methods, fields etc) */
protected final AnnotatedClass _classDef; protected final VisibilityChecker<?> _visibilityChecker; protected final AnnotationIntrospector _annotationIntrospector;
Since:2.9
/** * @since 2.9 */
protected final boolean _useAnnotations;
Prefix used by auto-detected mutators ("setters"): usually "set", but differs for builder objects ("with" by default).
/** * Prefix used by auto-detected mutators ("setters"): usually "set", * but differs for builder objects ("with" by default). */
protected final String _mutatorPrefix; /* /********************************************************** /* Collected property information /********************************************************** */
State flag we keep to indicate whether actual property information has been collected or not.
/** * State flag we keep to indicate whether actual property information * has been collected or not. */
protected boolean _collected;
Set of logical property information collected so far.

Since 2.6, this has been constructed (more) lazily, to defer throwing of exceptions for potential conflicts in cases where this may not be an actual problem.

/** * Set of logical property information collected so far. *<p> * Since 2.6, this has been constructed (more) lazily, to defer * throwing of exceptions for potential conflicts in cases where * this may not be an actual problem. */
protected LinkedHashMap<String, POJOPropertyBuilder> _properties; protected LinkedList<POJOPropertyBuilder> _creatorProperties;
A set of "field renamings" that have been discovered, indicating intended renaming of other accesors: key is the implicit original name and value intended name to use instead.

Note that these renamings are applied earlier than "regular" (explicit) renamings and affect implicit name: their effect may be changed by further renaming based on explicit indicators. The main use case is to effectively relink accessors based on fields discovered, and used to sort of correct otherwise missing linkage between fields and other accessors.

Since:2.11
/** * A set of "field renamings" that have been discovered, indicating * intended renaming of other accesors: key is the implicit original * name and value intended name to use instead. *<p> * Note that these renamings are applied earlier than "regular" (explicit) * renamings and affect implicit name: their effect may be changed by * further renaming based on explicit indicators. * The main use case is to effectively relink accessors based on fields * discovered, and used to sort of correct otherwise missing linkage between * fields and other accessors. * * @since 2.11 */
protected Map<PropertyName, PropertyName> _fieldRenameMappings; protected LinkedList<AnnotatedMember> _anyGetters; protected LinkedList<AnnotatedMethod> _anySetters; protected LinkedList<AnnotatedMember> _anySetterField;
Method(s) marked with 'JsonValue' annotation

NOTE: before 2.9, was `AnnotatedMethod`; with 2.9 allows fields too

/** * Method(s) marked with 'JsonValue' annotation *<p> * NOTE: before 2.9, was `AnnotatedMethod`; with 2.9 allows fields too */
protected LinkedList<AnnotatedMember> _jsonValueAccessors;
Lazily collected list of properties that can be implicitly ignored during serialization; only updated when collecting information for deserialization purposes
/** * Lazily collected list of properties that can be implicitly * ignored during serialization; only updated when collecting * information for deserialization purposes */
protected HashSet<String> _ignoredPropertyNames;
Lazily collected list of members that were annotated to indicate that they represent mutators for deserializer value injection.
/** * Lazily collected list of members that were annotated to * indicate that they represent mutators for deserializer * value injection. */
protected LinkedHashMap<Object, AnnotatedMember> _injectables; /* /********************************************************** /* Life-cycle /********************************************************** */ protected POJOPropertiesCollector(MapperConfig<?> config, boolean forSerialization, JavaType type, AnnotatedClass classDef, String mutatorPrefix) { _config = config; _stdBeanNaming = config.isEnabled(MapperFeature.USE_STD_BEAN_NAMING); _forSerialization = forSerialization; _type = type; _classDef = classDef; _mutatorPrefix = (mutatorPrefix == null) ? "set" : mutatorPrefix; if (config.isAnnotationProcessingEnabled()) { _useAnnotations = true; _annotationIntrospector = _config.getAnnotationIntrospector(); } else { _useAnnotations = false; _annotationIntrospector = AnnotationIntrospector.nopInstance(); } _visibilityChecker = _config.getDefaultVisibilityChecker(type.getRawClass(), classDef); } /* /********************************************************** /* Public API /********************************************************** */ public MapperConfig<?> getConfig() { return _config; } public JavaType getType() { return _type; } public AnnotatedClass getClassDef() { return _classDef; } public AnnotationIntrospector getAnnotationIntrospector() { return _annotationIntrospector; } public List<BeanPropertyDefinition> getProperties() { // make sure we return a copy, so caller can remove entries if need be: Map<String, POJOPropertyBuilder> props = getPropertyMap(); return new ArrayList<BeanPropertyDefinition>(props.values()); } public Map<Object, AnnotatedMember> getInjectables() { if (!_collected) { collectAll(); } return _injectables; }
Since:2.9
/** * @since 2.9 */
public AnnotatedMember getJsonValueAccessor() { if (!_collected) { collectAll(); } // If @JsonValue defined, must have a single one if (_jsonValueAccessors != null) { if (_jsonValueAccessors.size() > 1) { reportProblem("Multiple 'as-value' properties defined (%s vs %s)", _jsonValueAccessors.get(0), _jsonValueAccessors.get(1)); } // otherwise we won't greatly care return _jsonValueAccessors.get(0); } return null; } public AnnotatedMember getAnyGetter() { if (!_collected) { collectAll(); } if (_anyGetters != null) { if (_anyGetters.size() > 1) { reportProblem("Multiple 'any-getters' defined (%s vs %s)", _anyGetters.get(0), _anyGetters.get(1)); } return _anyGetters.getFirst(); } return null; } public AnnotatedMember getAnySetterField() { if (!_collected) { collectAll(); } if (_anySetterField != null) { if (_anySetterField.size() > 1) { reportProblem("Multiple 'any-setter' fields defined (%s vs %s)", _anySetterField.get(0), _anySetterField.get(1)); } return _anySetterField.getFirst(); } return null; } public AnnotatedMethod getAnySetterMethod() { if (!_collected) { collectAll(); } if (_anySetters != null) { if (_anySetters.size() > 1) { reportProblem("Multiple 'any-setter' methods defined (%s vs %s)", _anySetters.get(0), _anySetters.get(1)); } return _anySetters.getFirst(); } return null; }
Accessor for set of properties that are explicitly marked to be ignored via per-property markers (but NOT class annotations).
/** * Accessor for set of properties that are explicitly marked to be ignored * via per-property markers (but NOT class annotations). */
public Set<String> getIgnoredPropertyNames() { return _ignoredPropertyNames; }
Accessor to find out whether type specified requires inclusion of Object Identifier.
/** * Accessor to find out whether type specified requires inclusion * of Object Identifier. */
public ObjectIdInfo getObjectIdInfo() { ObjectIdInfo info = _annotationIntrospector.findObjectIdInfo(_classDef); if (info != null) { // 2.1: may also have different defaults for refs: info = _annotationIntrospector.findObjectReferenceInfo(_classDef, info); } return info; } // for unit tests: protected Map<String, POJOPropertyBuilder> getPropertyMap() { if (!_collected) { collectAll(); } return _properties; } @Deprecated // since 2.9 public AnnotatedMethod getJsonValueMethod() { AnnotatedMember m = getJsonValueAccessor(); if (m instanceof AnnotatedMethod) { return (AnnotatedMethod) m; } return null; } @Deprecated // since 2.11 (not used by anything at this point) public Class<?> findPOJOBuilderClass() { return _annotationIntrospector.findPOJOBuilder(_classDef); } /* /********************************************************** /* Public API: main-level collection /********************************************************** */
Internal method that will collect actual property information.
Since:2.6
/** * Internal method that will collect actual property information. * * @since 2.6 */
protected void collectAll() { LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>(); // First: gather basic data _addFields(props); // note: populates _fieldRenameMappings _addMethods(props); // 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static // inner classes, see [databind#1502] if (!_classDef.isNonStaticInnerClass()) { _addCreators(props); } // Remove ignored properties, first; this MUST precede annotation merging // since logic relies on knowing exactly which accessor has which annotation _removeUnwantedProperties(props); // and then remove unneeded accessors (wrt read-only, read-write) _removeUnwantedAccessor(props); // Rename remaining properties _renameProperties(props); // and now add injectables, but taking care to avoid overlapping ones // via creator and regular properties _addInjectables(props); // then merge annotations, to simplify further processing // 26-Sep-2017, tatu: Before 2.9.2 was done earlier but that prevented some of // annotations from getting properly merged for (POJOPropertyBuilder property : props.values()) { property.mergeAnnotations(_forSerialization); } // And use custom naming strategy, if applicable... PropertyNamingStrategy naming = _findNamingStrategy(); if (naming != null) { _renameUsing(props, naming); } // Sort by visibility (explicit over implicit); drop all but first of member // type (getter, setter etc) if there is visibility difference for (POJOPropertyBuilder property : props.values()) { property.trimByVisibility(); } // and, if required, apply wrapper name: note, MUST be done after // annotations are merged. if (_config.isEnabled(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME)) { _renameWithWrappers(props); } // well, almost last: there's still ordering... _sortProperties(props); _properties = props; _collected = true; } /* /********************************************************** /* Overridable internal methods, adding members /********************************************************** */
Method for collecting basic information on all fields found
/** * Method for collecting basic information on all fields found */
protected void _addFields(Map<String, POJOPropertyBuilder> props) { final AnnotationIntrospector ai = _annotationIntrospector; /* 28-Mar-2013, tatu: For deserialization we may also want to remove * final fields, as often they won't make very good mutators... * (although, maybe surprisingly, JVM _can_ force setting of such fields!) */ final boolean pruneFinalFields = !_forSerialization && !_config.isEnabled(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS); final boolean transientAsIgnoral = _config.isEnabled(MapperFeature.PROPAGATE_TRANSIENT_MARKER); for (AnnotatedField f : _classDef.fields()) { // @JsonValue? if (Boolean.TRUE.equals(ai.hasAsValue(f))) { if (_jsonValueAccessors == null) { _jsonValueAccessors = new LinkedList<>(); } _jsonValueAccessors.add(f); continue; } // @JsonAnySetter? if (Boolean.TRUE.equals(ai.hasAnySetter(f))) { if (_anySetterField == null) { _anySetterField = new LinkedList<AnnotatedMember>(); } _anySetterField.add(f); continue; } String implName = ai.findImplicitPropertyName(f); if (implName == null) { implName = f.getName(); } final PropertyName implNameP = _propNameFromSimple(implName); // [databind#2527: Field-based renaming can be applied early (here), // or at a later point, but probably must be done before pruning // final fields. So let's do it early here final PropertyName rename = ai.findRenameByField(_config, f, implNameP); if ((rename != null) && !rename.equals(implNameP)) { if (_fieldRenameMappings == null) { _fieldRenameMappings = new HashMap<>(); } _fieldRenameMappings.put(rename, implNameP); // todo } PropertyName pn; if (_forSerialization) { // 18-Aug-2011, tatu: As per existing unit tests, we should only // use serialization annotation (@JsonSerialize) when serializing // fields, and similarly for deserialize-only annotations... so // no fallbacks in this particular case. pn = ai.findNameForSerialization(f); } else { pn = ai.findNameForDeserialization(f); } boolean hasName = (pn != null); boolean nameExplicit = hasName; if (nameExplicit && pn.isEmpty()) { // empty String meaning "use default name", here just means "same as field name" pn = _propNameFromSimple(implName); nameExplicit = false; } // having explicit name means that field is visible; otherwise need to check the rules boolean visible = (pn != null); if (!visible) { visible = _visibilityChecker.isFieldVisible(f); } // and finally, may also have explicit ignoral boolean ignored = ai.hasIgnoreMarker(f); // 13-May-2015, tatu: Moved from earlier place (AnnotatedClass) in 2.6 if (f.isTransient()) { // 20-May-2016, tatu: as per [databind#1184] explicit annotation should override // "default" `transient` if (!hasName) { visible = false; if (transientAsIgnoral) { ignored = true; } } } /* [databind#190]: this is the place to prune final fields, if they are not * to be used as mutators. Must verify they are not explicitly included. * Also: if 'ignored' is set, need to include until a later point, to * avoid losing ignoral information. */ if (pruneFinalFields && (pn == null) && !ignored && Modifier.isFinal(f.getModifiers())) { continue; } _property(props, implName).addField(f, pn, nameExplicit, visible, ignored); } }
Method for collecting basic information on constructor(s) found
/** * Method for collecting basic information on constructor(s) found */
protected void _addCreators(Map<String, POJOPropertyBuilder> props) { // can be null if annotation processing is disabled... if (!_useAnnotations) { return; } for (AnnotatedConstructor ctor : _classDef.getConstructors()) { if (_creatorProperties == null) { _creatorProperties = new LinkedList<POJOPropertyBuilder>(); } for (int i = 0, len = ctor.getParameterCount(); i < len; ++i) { _addCreatorParam(props, ctor.getParameter(i)); } } for (AnnotatedMethod factory : _classDef.getFactoryMethods()) { if (_creatorProperties == null) { _creatorProperties = new LinkedList<POJOPropertyBuilder>(); } for (int i = 0, len = factory.getParameterCount(); i < len; ++i) { _addCreatorParam(props, factory.getParameter(i)); } } }
Since:2.4
/** * @since 2.4 */
protected void _addCreatorParam(Map<String, POJOPropertyBuilder> props, AnnotatedParameter param) { // JDK 8, paranamer, Scala can give implicit name String impl = _annotationIntrospector.findImplicitPropertyName(param); if (impl == null) { impl = ""; } PropertyName pn = _annotationIntrospector.findNameForDeserialization(param); boolean expl = (pn != null && !pn.isEmpty()); if (!expl) { if (impl.isEmpty()) { // Important: if neither implicit nor explicit name, cannot make use of // this creator parameter -- may or may not be a problem, verified at a later point. return; } // Also: if this occurs, there MUST be explicit annotation on creator itself JsonCreator.Mode creatorMode = _annotationIntrospector.findCreatorAnnotation(_config, param.getOwner()); if ((creatorMode == null) || (creatorMode == JsonCreator.Mode.DISABLED)) { return; } pn = PropertyName.construct(impl); } // 27-Dec-2019, tatu: [databind#2527] may need to rename according to field impl = _checkRenameByField(impl); // shouldn't need to worry about @JsonIgnore, since creators only added // if so annotated /* 13-May-2015, tatu: We should try to start with implicit name, similar to how * fields and methods work; but unlike those, we don't necessarily have * implicit name to use (pre-Java8 at least). So: */ POJOPropertyBuilder prop = (expl && impl.isEmpty()) ? _property(props, pn) : _property(props, impl); prop.addCtor(param, pn, expl, true, false); _creatorProperties.add(prop); }
Method for collecting basic information on all fields found
/** * Method for collecting basic information on all fields found */
protected void _addMethods(Map<String, POJOPropertyBuilder> props) { final AnnotationIntrospector ai = _annotationIntrospector; for (AnnotatedMethod m : _classDef.memberMethods()) { // For methods, handling differs between getters and setters; and // we will also only consider entries that either follow the bean // naming convention or are explicitly marked: just being visible // is not enough (unlike with fields) int argCount = m.getParameterCount(); if (argCount == 0) { // getters (including 'any getter') _addGetterMethod(props, m, ai); } else if (argCount == 1) { // setters _addSetterMethod(props, m, ai); } else if (argCount == 2) { // any getter? if (ai != null) { if (Boolean.TRUE.equals(ai.hasAnySetter(m))) { if (_anySetters == null) { _anySetters = new LinkedList<AnnotatedMethod>(); } _anySetters.add(m); } } } } } protected void _addGetterMethod(Map<String, POJOPropertyBuilder> props, AnnotatedMethod m, AnnotationIntrospector ai) { // Very first thing: skip if not returning any value if (!m.hasReturnType()) { return; } // any getter? // @JsonAnyGetter? if (Boolean.TRUE.equals(ai.hasAnyGetter(m))) { if (_anyGetters == null) { _anyGetters = new LinkedList<AnnotatedMember>(); } _anyGetters.add(m); return; } // @JsonValue? if (Boolean.TRUE.equals(ai.hasAsValue(m))) { if (_jsonValueAccessors == null) { _jsonValueAccessors = new LinkedList<>(); } _jsonValueAccessors.add(m); return; } String implName; // from naming convention boolean visible; PropertyName pn = ai.findNameForSerialization(m); boolean nameExplicit = (pn != null); if (!nameExplicit) { // no explicit name; must consider implicit implName = ai.findImplicitPropertyName(m); if (implName == null) { implName = BeanUtil.okNameForRegularGetter(m, m.getName(), _stdBeanNaming); } if (implName == null) { // if not, must skip implName = BeanUtil.okNameForIsGetter(m, m.getName(), _stdBeanNaming); if (implName == null) { return; } visible = _visibilityChecker.isIsGetterVisible(m); } else { visible = _visibilityChecker.isGetterVisible(m); } } else { // explicit indication of inclusion, but may be empty // we still need implicit name to link with other pieces implName = ai.findImplicitPropertyName(m); if (implName == null) { implName = BeanUtil.okNameForGetter(m, _stdBeanNaming); } // if not regular getter name, use method name as is if (implName == null) { implName = m.getName(); } if (pn.isEmpty()) { // !!! TODO: use PropertyName for implicit names too pn = _propNameFromSimple(implName); nameExplicit = false; } visible = true; } // 27-Dec-2019, tatu: [databind#2527] may need to rename according to field implName = _checkRenameByField(implName); boolean ignore = ai.hasIgnoreMarker(m); _property(props, implName).addGetter(m, pn, nameExplicit, visible, ignore); } protected void _addSetterMethod(Map<String, POJOPropertyBuilder> props, AnnotatedMethod m, AnnotationIntrospector ai) { String implName; // from naming convention boolean visible; PropertyName pn = (ai == null) ? null : ai.findNameForDeserialization(m); boolean nameExplicit = (pn != null); if (!nameExplicit) { // no explicit name; must follow naming convention implName = (ai == null) ? null : ai.findImplicitPropertyName(m); if (implName == null) { implName = BeanUtil.okNameForMutator(m, _mutatorPrefix, _stdBeanNaming); } if (implName == null) { // if not, must skip return; } visible = _visibilityChecker.isSetterVisible(m); } else { // explicit indication of inclusion, but may be empty // we still need implicit name to link with other pieces implName = (ai == null) ? null : ai.findImplicitPropertyName(m); if (implName == null) { implName = BeanUtil.okNameForMutator(m, _mutatorPrefix, _stdBeanNaming); } // if not regular getter name, use method name as is if (implName == null) { implName = m.getName(); } if (pn.isEmpty()) { // !!! TODO: use PropertyName for implicit names too pn = _propNameFromSimple(implName); nameExplicit = false; } visible = true; } // 27-Dec-2019, tatu: [databind#2527] may need to rename according to field implName = _checkRenameByField(implName); boolean ignore = (ai == null) ? false : ai.hasIgnoreMarker(m); _property(props, implName).addSetter(m, pn, nameExplicit, visible, ignore); } protected void _addInjectables(Map<String, POJOPropertyBuilder> props) { final AnnotationIntrospector ai = _annotationIntrospector; // first fields, then methods, to allow overriding for (AnnotatedField f : _classDef.fields()) { _doAddInjectable(ai.findInjectableValue(f), f); } for (AnnotatedMethod m : _classDef.memberMethods()) { // for now, only allow injection of a single arg (to be changed in future?) if (m.getParameterCount() != 1) { continue; } _doAddInjectable(ai.findInjectableValue(m), m); } } protected void _doAddInjectable(JacksonInject.Value injectable, AnnotatedMember m) { if (injectable == null) { return; } Object id = injectable.getId(); if (_injectables == null) { _injectables = new LinkedHashMap<Object, AnnotatedMember>(); } AnnotatedMember prev = _injectables.put(id, m); if (prev != null) { // 12-Apr-2017, tatu: Let's allow masking of Field by Method if (prev.getClass() == m.getClass()) { String type = id.getClass().getName(); throw new IllegalArgumentException("Duplicate injectable value with id '" +String.valueOf(id)+"' (of type "+type+")"); } } } private PropertyName _propNameFromSimple(String simpleName) { return PropertyName.construct(simpleName, null); } // @since 2.11 private String _checkRenameByField(String implName) { if (_fieldRenameMappings != null) { PropertyName p = _fieldRenameMappings.get(_propNameFromSimple(implName)); if (p != null) { implName = p.getSimpleName(); return implName; } } return implName; } /* /********************************************************** /* Internal methods; removing ignored properties /********************************************************** */
Method called to get rid of candidate properties that are marked as ignored.
/** * Method called to get rid of candidate properties that are marked * as ignored. */
protected void _removeUnwantedProperties(Map<String, POJOPropertyBuilder> props) { Iterator<POJOPropertyBuilder> it = props.values().iterator(); while (it.hasNext()) { POJOPropertyBuilder prop = it.next(); // First: if nothing visible, just remove altogether if (!prop.anyVisible()) { it.remove(); continue; } // Otherwise, check ignorals if (prop.anyIgnorals()) { // first: if one or more ignorals, and no explicit markers, remove the whole thing if (!prop.isExplicitlyIncluded()) { it.remove(); _collectIgnorals(prop.getName()); continue; } // otherwise just remove ones marked to be ignored prop.removeIgnored(); if (!prop.couldDeserialize()) { _collectIgnorals(prop.getName()); } } } }
Method called to further get rid of unwanted individual accessors, based on read/write settings and rules for "pulling in" accessors (or not).
/** * Method called to further get rid of unwanted individual accessors, * based on read/write settings and rules for "pulling in" accessors * (or not). */
protected void _removeUnwantedAccessor(Map<String, POJOPropertyBuilder> props) { final boolean inferMutators = _config.isEnabled(MapperFeature.INFER_PROPERTY_MUTATORS); Iterator<POJOPropertyBuilder> it = props.values().iterator(); while (it.hasNext()) { POJOPropertyBuilder prop = it.next(); // 26-Jan-2017, tatu: [databind#935]: need to denote removal of JsonProperty.Access acc = prop.removeNonVisible(inferMutators); if (acc == JsonProperty.Access.READ_ONLY) { _collectIgnorals(prop.getName()); } } }
Helper method called to add explicitly ignored properties to a list of known ignored properties; this helps in proper reporting of errors.
/** * Helper method called to add explicitly ignored properties to a list * of known ignored properties; this helps in proper reporting of * errors. */
private void _collectIgnorals(String name) { if (!_forSerialization) { if (_ignoredPropertyNames == null) { _ignoredPropertyNames = new HashSet<String>(); } _ignoredPropertyNames.add(name); } } /* /********************************************************** /* Internal methods; renaming properties /********************************************************** */ protected void _renameProperties(Map<String, POJOPropertyBuilder> props) { // With renaming need to do in phases: first, find properties to rename Iterator<Map.Entry<String,POJOPropertyBuilder>> it = props.entrySet().iterator(); LinkedList<POJOPropertyBuilder> renamed = null; while (it.hasNext()) { Map.Entry<String, POJOPropertyBuilder> entry = it.next(); POJOPropertyBuilder prop = entry.getValue(); Collection<PropertyName> l = prop.findExplicitNames(); // no explicit names? Implicit one is fine as is if (l.isEmpty()) { continue; } it.remove(); // need to replace with one or more renamed if (renamed == null) { renamed = new LinkedList<POJOPropertyBuilder>(); } // simple renaming? Just do it if (l.size() == 1) { PropertyName n = l.iterator().next(); renamed.add(prop.withName(n)); continue; } // but this may be problematic... renamed.addAll(prop.explode(l)); /* String newName = prop.findNewName(); if (newName != null) { if (renamed == null) { renamed = new LinkedList<POJOPropertyBuilder>(); } prop = prop.withSimpleName(newName); renamed.add(prop); it.remove(); } */ } // and if any were renamed, merge back in... if (renamed != null) { for (POJOPropertyBuilder prop : renamed) { String name = prop.getName(); POJOPropertyBuilder old = props.get(name); if (old == null) { props.put(name, prop); } else { old.addAll(prop); } // replace the creatorProperty too, if there is one _updateCreatorProperty(prop, _creatorProperties); // [databind#2001]: New name of property was ignored previously? Remove from ignored // 01-May-2018, tatu: I have a feeling this will need to be revisited at some point, // to avoid removing some types of removals, possibly. But will do for now. if (_ignoredPropertyNames != null) { _ignoredPropertyNames.remove(name); } } } } protected void _renameUsing(Map<String, POJOPropertyBuilder> propMap, PropertyNamingStrategy naming) { POJOPropertyBuilder[] props = propMap.values().toArray(new POJOPropertyBuilder[propMap.size()]); propMap.clear(); for (POJOPropertyBuilder prop : props) { PropertyName fullName = prop.getFullName(); String rename = null; // As per [databind#428] need to skip renaming if property has // explicitly defined name, unless feature is enabled if (!prop.isExplicitlyNamed() || _config.isEnabled(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING)) { if (_forSerialization) { if (prop.hasGetter()) { rename = naming.nameForGetterMethod(_config, prop.getGetter(), fullName.getSimpleName()); } else if (prop.hasField()) { rename = naming.nameForField(_config, prop.getField(), fullName.getSimpleName()); } } else { if (prop.hasSetter()) { rename = naming.nameForSetterMethod(_config, prop.getSetter(), fullName.getSimpleName()); } else if (prop.hasConstructorParameter()) { rename = naming.nameForConstructorParameter(_config, prop.getConstructorParameter(), fullName.getSimpleName()); } else if (prop.hasField()) { rename = naming.nameForField(_config, prop.getField(), fullName.getSimpleName()); } else if (prop.hasGetter()) { /* Plus, when getter-as-setter is used, need to convert that too.. * (should we verify that's enabled? For now, assume it's ok always) */ rename = naming.nameForGetterMethod(_config, prop.getGetter(), fullName.getSimpleName()); } } } final String simpleName; if ((rename != null) && !fullName.hasSimpleName(rename)) { prop = prop.withSimpleName(rename); simpleName = rename; } else { simpleName = fullName.getSimpleName(); } // Need to consider case where there may already be something in there... POJOPropertyBuilder old = propMap.get(simpleName); if (old == null) { propMap.put(simpleName, prop); } else { old.addAll(prop); } // replace the creatorProperty too, if there is one _updateCreatorProperty(prop, _creatorProperties); } } protected void _renameWithWrappers(Map<String, POJOPropertyBuilder> props) { // 11-Sep-2012, tatu: To support 'MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME', // need another round of renaming... Iterator<Map.Entry<String,POJOPropertyBuilder>> it = props.entrySet().iterator(); LinkedList<POJOPropertyBuilder> renamed = null; while (it.hasNext()) { Map.Entry<String, POJOPropertyBuilder> entry = it.next(); POJOPropertyBuilder prop = entry.getValue(); AnnotatedMember member = prop.getPrimaryMember(); if (member == null) { continue; } PropertyName wrapperName = _annotationIntrospector.findWrapperName(member); // One trickier part (wrt [#24] of JAXB annotations: wrapper that // indicates use of actual property... But hopefully has been taken care // of previously if (wrapperName == null || !wrapperName.hasSimpleName()) { continue; } if (!wrapperName.equals(prop.getFullName())) { if (renamed == null) { renamed = new LinkedList<POJOPropertyBuilder>(); } prop = prop.withName(wrapperName); renamed.add(prop); it.remove(); } } // and if any were renamed, merge back in... if (renamed != null) { for (POJOPropertyBuilder prop : renamed) { String name = prop.getName(); POJOPropertyBuilder old = props.get(name); if (old == null) { props.put(name, prop); } else { old.addAll(prop); } } } } /* /********************************************************** /* Overridable internal methods, sorting, other stuff /********************************************************** */ // First, order by(explicit ordering and/or alphabetic), // then by (optional) index (if any) // and then implicitly order creator properties before others) protected void _sortProperties(Map<String, POJOPropertyBuilder> props) { // Then how about explicit ordering? final AnnotationIntrospector intr = _annotationIntrospector; Boolean alpha = intr.findSerializationSortAlphabetically((Annotated) _classDef); final boolean sort = (alpha == null) ? _config.shouldSortPropertiesAlphabetically() : alpha.booleanValue(); final boolean indexed = _anyIndexed(props.values()); String[] propertyOrder = intr.findSerializationPropertyOrder(_classDef); // no sorting? no need to shuffle, then if (!sort && !indexed && (_creatorProperties == null) && (propertyOrder == null)) { return; } int size = props.size(); Map<String, POJOPropertyBuilder> all; // Need to (re)sort alphabetically? if (sort) { all = new TreeMap<String,POJOPropertyBuilder>(); } else { all = new LinkedHashMap<String,POJOPropertyBuilder>(size+size); } for (POJOPropertyBuilder prop : props.values()) { all.put(prop.getName(), prop); } Map<String,POJOPropertyBuilder> ordered = new LinkedHashMap<>(size+size); // Ok: primarily by explicit order if (propertyOrder != null) { for (String name : propertyOrder) { POJOPropertyBuilder w = all.remove(name); if (w == null) { // will also allow use of "implicit" names for sorting for (POJOPropertyBuilder prop : props.values()) { if (name.equals(prop.getInternalName())) { w = prop; // plus re-map to external name, to avoid dups: name = prop.getName(); break; } } } if (w != null) { ordered.put(name, w); } } } // Second (starting with 2.11): index, if any: if (indexed) { Map<Integer,POJOPropertyBuilder> byIndex = new TreeMap<>(); Iterator<Map.Entry<String,POJOPropertyBuilder>> it = all.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String,POJOPropertyBuilder> entry = it.next(); POJOPropertyBuilder prop = entry.getValue(); Integer index = prop.getMetadata().getIndex(); if (index != null) { byIndex.put(index, prop); it.remove(); } } for (POJOPropertyBuilder prop : byIndex.values()) { ordered.put(prop.getName(), prop); } } // Third by sorting Creator properties before other unordered properties if (_creatorProperties != null) { /* As per [databind#311], this is bit delicate; but if alphabetic ordering * is mandated, at least ensure creator properties are in alphabetic * order. Related question of creator vs non-creator is punted for now, * so creator properties still fully predate non-creator ones. */ Collection<POJOPropertyBuilder> cr; if (sort) { TreeMap<String, POJOPropertyBuilder> sorted = new TreeMap<String,POJOPropertyBuilder>(); for (POJOPropertyBuilder prop : _creatorProperties) { sorted.put(prop.getName(), prop); } cr = sorted.values(); } else { cr = _creatorProperties; } for (POJOPropertyBuilder prop : cr) { // 16-Jan-2016, tatu: Related to [databind#1317], make sure not to accidentally // add back pruned creator properties! String name = prop.getName(); // 27-Nov-2019, tatu: Not sure why, but we should NOT remove it from `all` tho: // if (all.remove(name) != null) { if (all.containsKey(name)) { ordered.put(name, prop); } } } // And finally whatever is left (trying to put again will not change ordering) ordered.putAll(all); props.clear(); props.putAll(ordered); } private boolean _anyIndexed(Collection<POJOPropertyBuilder> props) { for (POJOPropertyBuilder prop : props) { if (prop.getMetadata().hasIndex()) { return true; } } return false; } /* /********************************************************** /* Internal methods; helpers /********************************************************** */ protected void reportProblem(String msg, Object... args) { if (args.length > 0) { msg = String.format(msg, args); } throw new IllegalArgumentException("Problem with definition of "+_classDef+": "+msg); } protected POJOPropertyBuilder _property(Map<String, POJOPropertyBuilder> props, PropertyName name) { String simpleName = name.getSimpleName(); POJOPropertyBuilder prop = props.get(simpleName); if (prop == null) { prop = new POJOPropertyBuilder(_config, _annotationIntrospector, _forSerialization, name); props.put(simpleName, prop); } return prop; } // !!! TODO: deprecate, require use of PropertyName protected POJOPropertyBuilder _property(Map<String, POJOPropertyBuilder> props, String implName) { POJOPropertyBuilder prop = props.get(implName); if (prop == null) { prop = new POJOPropertyBuilder(_config, _annotationIntrospector, _forSerialization, PropertyName.construct(implName)); props.put(implName, prop); } return prop; } private PropertyNamingStrategy _findNamingStrategy() { Object namingDef = _annotationIntrospector.findNamingStrategy(_classDef); if (namingDef == null) { return _config.getPropertyNamingStrategy(); } if (namingDef instanceof PropertyNamingStrategy) { return (PropertyNamingStrategy) namingDef; } /* Alas, there's no way to force return type of "either class * X or Y" -- need to throw an exception after the fact */ if (!(namingDef instanceof Class)) { throw new IllegalStateException("AnnotationIntrospector returned PropertyNamingStrategy definition of type " +namingDef.getClass().getName()+"; expected type PropertyNamingStrategy or Class<PropertyNamingStrategy> instead"); } Class<?> namingClass = (Class<?>)namingDef; // 09-Nov-2015, tatu: Need to consider pseudo-value of STD, which means "use default" if (namingClass == PropertyNamingStrategy.class) { return null; } if (!PropertyNamingStrategy.class.isAssignableFrom(namingClass)) { throw new IllegalStateException("AnnotationIntrospector returned Class " +namingClass.getName()+"; expected Class<PropertyNamingStrategy>"); } HandlerInstantiator hi = _config.getHandlerInstantiator(); if (hi != null) { PropertyNamingStrategy pns = hi.namingStrategyInstance(_config, _classDef, namingClass); if (pns != null) { return pns; } } return (PropertyNamingStrategy) ClassUtil.createInstance(namingClass, _config.canOverrideAccessModifiers()); } protected void _updateCreatorProperty(POJOPropertyBuilder prop, List<POJOPropertyBuilder> creatorProperties) { if (creatorProperties != null) { final String intName = prop.getInternalName(); for (int i = 0, len = creatorProperties.size(); i < len; ++i) { if (creatorProperties.get(i).getInternalName().equals(intName)) { creatorProperties.set(i, prop); break; } } } } }