package com.fasterxml.jackson.jr.annotationsupport;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.jr.ob.impl.JSONReader;
import com.fasterxml.jackson.jr.ob.impl.JSONWriter;
import com.fasterxml.jackson.jr.ob.impl.POJODefinition;

Since:2.11
/** * * @since 2.11 */
public class AnnotationBasedIntrospector { // // // Configuration protected final Class<?> _type; protected final boolean _forSerialization;
Visibility settings to use for auto-detecting accessors.
/** * Visibility settings to use for auto-detecting accessors. */
protected final JsonAutoDetect.Value _visibility; // // // State (collected properties, related) protected final Map<String, APropBuilder> _props = new HashMap<String, APropBuilder>(); // // // State only for deserialization: protected Set<String> _ignorableNames; protected AnnotationBasedIntrospector(Class<?> type, boolean serialization, JsonAutoDetect.Value visibility) { _type = type; _forSerialization = serialization; _ignorableNames = serialization ? null : new HashSet<String>(); // First things first: find possible `@JsonAutoDetect` to override // default visibility settings JsonAutoDetect ann = _find(type, JsonAutoDetect.class); // bad form but... if (ann == null) { _visibility = visibility; } else { _visibility = visibility.withOverrides(JsonAutoDetect.Value.from(ann)); } } public static POJODefinition pojoDefinitionForDeserialization(JSONReader r, Class<?> pojoType, JsonAutoDetect.Value visibility) { return new AnnotationBasedIntrospector(pojoType, false, visibility) .introspectDefinition(); } public static POJODefinition pojoDefinitionForSerialization(JSONWriter w, Class<?> pojoType, JsonAutoDetect.Value visibility) { return new AnnotationBasedIntrospector(pojoType, true, visibility) .introspectDefinition(); } /* /********************************************************************** /* Construction /********************************************************************** */ protected POJODefinition introspectDefinition() { _findFields(); _findMethods(); Constructor<?> defaultCtor = null; Constructor<?> stringCtor = null; Constructor<?> longCtor = null; // A few things only matter during deserialization: constructors, // secondary ignoral information: if (!_forSerialization) { for (Constructor<?> ctor : _type.getDeclaredConstructors()) { Class<?>[] argTypes = ctor.getParameterTypes(); if (argTypes.length == 0) { defaultCtor = ctor; } else if (argTypes.length == 1) { Class<?> argType = argTypes[0]; if (argType == String.class) { stringCtor = ctor; } else if (argType == Long.class || argType == Long.TYPE) { longCtor = ctor; } else { continue; } } else { continue; } } } POJODefinition def = new POJODefinition(_type, _pruneProperties(_forSerialization), defaultCtor, stringCtor, longCtor); if (_ignorableNames != null) { def = def.withIgnorals(_ignorableNames); } return def; } /* /********************************************************************** /* Internal methods, main introspection /********************************************************************** */ protected POJODefinition.Prop[] _pruneProperties(boolean sortProperties) { // First round: entry removal, collections of things to rename List<APropBuilder> renamed = null; Iterator<APropBuilder> it = _props.values().iterator(); while (it.hasNext()) { final APropBuilder prop = it.next(); // Start with ignorals, since those can be used as marker for otherwise // unknown properties if (prop.anyIgnorals()) { // if one or more ignorals, and no explicit markers, remove the whole thing if (!prop.anyExplicit()) { it.remove(); _addIgnoral(prop.name); } else { // otherwise just remove ones marked to be ignored prop.removeIgnored(); if (!prop.couldDeserialize()) { _addIgnoral(prop.name); } } continue; } // but even without ignorals, something has to be visible; if not, remove prop if (!prop.anyVisible()) { // if nothing visible, just remove altogether it.remove(); continue; } // plus then remove non-visible accessors prop.removeNonVisible(); // and finally, see if renaming (due to explicit name override) needed: String explName = prop.findPrimaryExplicitName(_forSerialization); if (explName != null) { it.remove(); if (renamed == null) { renamed = new LinkedList<APropBuilder>(); } renamed.add(prop.withName(explName)); } } // If (but only if) renamings needed, re-process if (renamed != null) { for (APropBuilder prop : renamed) { APropBuilder orig = _props.get(prop.name); if (orig == null) { // Straight rename, no merge _props.put(prop.name, prop); continue; } APropBuilder merged = APropBuilder.merge(orig, prop); _props.put(prop.name, merged); } } // Next step: removal by `@JsonIgnoreProperties`: final Collection<String> ignorableNames = _findIgnorableNames(); if (!ignorableNames.isEmpty()) { if (_ignorableNames != null) { // may be needed for deserialization _ignorableNames.addAll(ignorableNames); } // but needs to be removed from set of known properties for both for (String ignorableName : ignorableNames) { _findAndRemoveByName(ignorableName); } } final int propCount = _props.size(); final POJODefinition.Prop[] result = new POJODefinition.Prop[propCount]; int i = 0; final boolean collectAliases = !_forSerialization; if (sortProperties) { // sorting? (yes for serialization, no for deser) // anything to sort by? List<String> nameOrder = _findNameSortOrder(); if (!nameOrder.isEmpty()) { for (String name : nameOrder) { APropBuilder prop = _findAndRemoveByName(name); if (prop != null) { result[i++] = prop.asProperty(collectAliases); } } } // and anything remaining, add alphabetically TreeMap<String, APropBuilder> sorted = new TreeMap<String, APropBuilder>(_props); // For now, order alphabetically (natural order by name) for (APropBuilder prop : sorted.values()) { result[i++] = prop.asProperty(collectAliases); } } else { for (APropBuilder prop : _props.values()) { result[i++] = prop.asProperty(collectAliases); } } return result; } protected void _findFields() { for (Field f : _type.getDeclaredFields()) { // Does not include static fields, but there are couple of things we do // not include regardless: if (f.isEnumConstant() || f.isSynthetic()) { continue; } // otherwise, first things first; explicit ignoral? final String implName = f.getName(); APropAccessor<Field> acc; if (Boolean.TRUE.equals(_hasIgnoreMarker(f))) { acc = APropAccessor.createIgnorable(implName, f); } else { final String explName = _findExplicitName(f); // Otherwise, do have explicit inclusion marker? if (explName != null) { // ... with actual name? if (explName.isEmpty()) { // `@JsonProperty("")` acc = APropAccessor.createVisible(implName, f); } else { acc = APropAccessor.createExplicit(explName, f); } } else { // Otherwise may be visible acc = APropAccessor.createImplicit(explName, f, _isFieldVisible(f)); } } _propBuilder(implName).field = acc; } } protected void _findMethods() { _findMethods(_type); } protected void _findMethods(final Class<?> currType) { if (currType == null || currType == Object.class) { return; } // Start with base type methods (so overrides work) _findMethods(currType.getSuperclass()); // then get methods from within this class for (Method m : currType.getDeclaredMethods()) { final int flags = m.getModifiers(); // 13-Jun-2015, tatu: Skip synthetic, bridge methods altogether, for now // at least (add more complex handling only if absolutely necessary) if (Modifier.isStatic(flags) || m.isSynthetic() || m.isBridge()) { continue; } int argCount = m.getParameterCount(); if (argCount == 0) { // getters (including 'any getter') _checkGetterMethod(m); } else if (argCount == 1) { // setters _checkSetterMethod(m); } } } protected void _checkGetterMethod(Method m) { Class<?> resultType = m.getReturnType(); if (resultType == Void.class) { return; } final String name0 = m.getName(); String implName = null; boolean isIsGetter = false; if (name0.startsWith("get")) { if (name0.length() > 3) { implName = _decap(name0.substring(3)); } } else if (name0.startsWith("is")) { if (name0.length() > 2) { // May or may not be used, but collect for now all the same: implName = _decap(name0.substring(2)); isIsGetter = true; } } APropAccessor<Method> acc; if (implName == null) { // does not follow naming convention; needs explicit final String explName = _findExplicitName(m); if (explName == null) { return; } implName = name0; // But let's first see if there is ignoral if (Boolean.TRUE.equals(_hasIgnoreMarker(m))) { // could just bail out as is, at this point? But there is explicit marker acc = APropAccessor.createIgnorable(implName, m); } else { if (explName.isEmpty()) { acc = APropAccessor.createVisible(implName, m); } else { acc = APropAccessor.createExplicit(explName, m); } } } else { // implicit name already, but ignoral/explicit? if (Boolean.TRUE.equals(_hasIgnoreMarker(m))) { acc = APropAccessor.createIgnorable(implName, m); } else { final String explName = _findExplicitName(m); if (explName == null) { acc = APropAccessor.createImplicit(implName, m, _isGetterVisible(m, isIsGetter)); } else if (explName.isEmpty()) { acc = APropAccessor.createVisible(implName, m); } else { acc = APropAccessor.createExplicit(explName, m); } } } _propBuilder(implName).getter = acc; } protected void _checkSetterMethod(Method m) { final String name0 = m.getName(); String implName; if (name0.startsWith("set") && (name0.length() > 3)) { implName = _decap(name0.substring(3)); } else { implName = null; } // Pretty much the same as with getters (just calls to couple of diff methods) APropAccessor<Method> acc; if (implName == null) { final String explName = _findExplicitName(m); if (explName == null) { return; } implName = name0; if (Boolean.TRUE.equals(_hasIgnoreMarker(m))) { acc = APropAccessor.createIgnorable(implName, m); } else { if (explName.isEmpty()) { acc = APropAccessor.createVisible(implName, m); } else { acc = APropAccessor.createExplicit(explName, m); } } } else { if (Boolean.TRUE.equals(_hasIgnoreMarker(m))) { acc = APropAccessor.createIgnorable(implName, m); } else { final String explName = _findExplicitName(m); if (explName == null) { acc = APropAccessor.createImplicit(implName, m, _isSetterVisible(m)); } else if (explName.isEmpty()) { acc = APropAccessor.createVisible(implName, m); } else { acc = APropAccessor.createExplicit(explName, m); } } } _propBuilder(implName).setter = acc; } /* /********************************************************************** /* Internal methods, visibility /********************************************************************** */ protected boolean _isFieldVisible(Field f) { // Consider transient to be non-visible // TODO: (maybe?) final return !Modifier.isTransient(f.getModifiers()) && _visibility.getFieldVisibility().isVisible(f); } protected boolean _isGetterVisible(Method m, boolean isIsGetter) { if (isIsGetter) { return _visibility.getIsGetterVisibility().isVisible(m); } return _visibility.getGetterVisibility().isVisible(m); } protected boolean _isSetterVisible(Method m) { return _visibility.getSetterVisibility().isVisible(m); } /* /********************************************************************** /* Internal methods, annotation introspection /********************************************************************** */ // wrapper type just in case in future we want to detect existence of disables // ignoral marker for some reason protected Boolean _hasIgnoreMarker(AnnotatedElement m) { JsonIgnore ann = _find(m, JsonIgnore.class); return (ann != null) && ann.value(); } protected String _findExplicitName(AnnotatedElement m) { JsonProperty ann = _find(m, JsonProperty.class); return (ann == null) ? null : ann.value(); }
Lookup method for finding possible annotated order of property names for the type this introspector is to introspect
Returns:List of property names that defines order (possibly partial); if none, empty List (but never null)
/** * Lookup method for finding possible annotated order of property names * for the type this introspector is to introspect * * @return List of property names that defines order (possibly partial); if * none, empty List (but never null) */
protected List<String> _findNameSortOrder() { JsonPropertyOrder ann = _find(_type, JsonPropertyOrder.class); if (ann == null) { return Collections.emptyList(); } return Arrays.asList(ann.value()); }
Lookup method for finding a set of property names for the type this introspector is to introspect that should be ignored (both for serialization and deserialization).
Returns:List of property names that defines order (possibly partial); if none, empty List (but never null)
/** * Lookup method for finding a set of property names * for the type this introspector is to introspect that should be ignored * (both for serialization and deserialization). * * @return List of property names that defines order (possibly partial); if * none, empty List (but never null) */
protected Collection<String> _findIgnorableNames() { JsonIgnoreProperties ann = _find(_type, JsonIgnoreProperties.class); if (ann == null) { return Collections.emptyList(); } return Arrays.asList(ann.value()); } // Overridable accessor method protected <ANN extends Annotation> ANN _find(AnnotatedElement elem, Class<ANN> annotationType) { return elem.getAnnotation(annotationType); } /* /********************************************************************** /* Internal methods, other /********************************************************************** */ protected APropBuilder _propBuilder(String name) { APropBuilder b = _props.get(name); if (b == null) { b = new APropBuilder(name); _props.put(name, b); } return b; } protected void _addIgnoral(String name) { if (_ignorableNames != null) { _ignorableNames.add(name); } }
Helper method for locating a property (builder) identified by given name (either primary, or secondary), and if found, removing from main properties Map, returning.
Params:
  • name – Name of property to find (either primary [checked first] or secondary)
Returns:Property (builder) if found; null if none
/** * Helper method for locating a property (builder) identified by given name * (either primary, or secondary), and if found, removing from main properties * Map, returning. * * @param name Name of property to find (either primary [checked first] or secondary) * * @return Property (builder) if found; {@code null} if none */
protected APropBuilder _findAndRemoveByName(String name) { APropBuilder prop = _props.remove(name); if (prop == null) { // Not located by primary, check secondary ('original' or 'internal' name) for (APropBuilder p2 : _props.values()) { prop = _props.remove(p2.origName); if (prop != null) { break; } } } return prop; } protected static String _decap(String name) { char c = name.charAt(0); char lowerC = Character.toLowerCase(c); if (c != lowerC) { // First: do NOT lower case if more than one leading upper case letters: if ((name.length() == 1) || !Character.isUpperCase(name.charAt(1))) { char chars[] = name.toCharArray(); chars[0] = lowerC; return new String(chars); } } return name; } /* /********************************************************************** /* Helper classes /********************************************************************** */ protected static class APropBuilder implements Comparable<APropBuilder> {
Initial name from accessor ("implicit" or "internal" name); not changed with renames
/** * Initial name from accessor ("implicit" or "internal" name); not * changed with renames */
public final String origName; public final String name; protected APropAccessor<Field> field; protected APropAccessor<Method> getter; protected APropAccessor<Method> setter; public APropBuilder(String n) { origName = n; name = n; } protected APropBuilder(APropBuilder base, String n) { origName = base.origName; name = n; } public POJODefinition.Prop asProperty(boolean collectAliases) { Set<String> aliases = collectAliases ? collectAliases() : null; return new POJODefinition.Prop(name, (field == null) ? null : field.accessor, (setter == null) ? null : setter.accessor, (getter == null) ? null : getter.accessor, /* isGetter */ null, aliases); } public static APropBuilder merge(APropBuilder b1, APropBuilder b2) { APropBuilder newB = new APropBuilder(b1.name); newB.field = _merge(b1.field, b2.field); newB.getter = _merge(b1.getter, b2.getter); newB.setter = _merge(b1.setter, b2.setter); return newB; } private static <A extends AccessibleObject> APropAccessor<A> _merge(APropAccessor<A> a1, APropAccessor<A> a2) { if (a1 == null) { return a2; } if (a2 == null) { return a1; } if (a1.isNameExplicit) { return a1; } if (a2.isNameExplicit) { return a2; } if (a1.isExplicit) { return a1; } if (a2.isExplicit) { return a2; } // Could try other things too (visibility, place in hierarchy) but... for now // should be fine to take first one return a1; } public APropBuilder withName(String newName) { APropBuilder newB = new APropBuilder(this, newName); newB.field = field; newB.getter = getter; newB.setter = setter; return newB; } public void removeIgnored() { if ((field != null) && field.isToIgnore) { field = null; } if ((getter != null) && getter.isToIgnore) { getter = null; } if ((setter != null) && setter.isToIgnore) { setter = null; } } public void removeNonVisible() { if ((field != null) && !field.isVisible) { field = null; } if ((getter != null) && !getter.isVisible) { getter = null; } if ((setter != null) && !setter.isVisible) { setter = null; } } public Set<String> collectAliases() { Set<String> collectedAliases = null; // although aliases only relevant for deserialization, collect from // all accessors nonetheless collectedAliases = _collectAliases(field, collectedAliases); collectedAliases = _collectAliases(getter, collectedAliases); collectedAliases = _collectAliases(setter, collectedAliases); return collectedAliases; } private static Set<String> _collectAliases(APropAccessor<?> acc, Set<String> collectedAliases) { if (acc != null) { AnnotatedElement accOb = acc.accessor; if (accOb != null) { JsonAlias ann = accOb.getAnnotation(JsonAlias.class); if (ann != null) { final String[] names = ann.value(); if (collectedAliases == null) { collectedAliases = new HashSet<String>(); } for (String alias : names) { collectedAliases.add(alias); } } } } return collectedAliases; } private String _firstExplicit(APropAccessor<?> acc1, APropAccessor<?> acc2, APropAccessor<?> acc3) { if (acc1 != null && acc1.isNameExplicit) { return acc1.name; } if (acc2 != null && acc2.isNameExplicit) { return acc2.name; } if (acc3 != null && acc3.isNameExplicit) { return acc3.name; } return null; } public String findPrimaryExplicitName(boolean forSer) { if (forSer) { return _firstExplicit(getter, setter, field); } return _firstExplicit(setter, getter, field); } public boolean anyVisible() { return ((field != null) && field.isVisible) || ((getter != null) && getter.isVisible) || ((setter != null) && setter.isVisible); } public boolean anyExplicit() { return ((field != null) && field.isExplicit) || ((getter != null) && getter.isExplicit) || ((setter != null) && setter.isExplicit); } public boolean anyIgnorals() { return ((field != null) && field.isToIgnore) || ((getter != null) && getter.isToIgnore) || ((setter != null) && setter.isToIgnore); } public boolean couldDeserialize() { return (field != null) || (setter != null); } @Override public int compareTo(APropBuilder o) { return name.compareTo(o.name); } } protected static class APropAccessor<ACC extends AccessibleObject> { public final String name; public final ACC accessor; public final boolean isExplicit, isNameExplicit; public final boolean isToIgnore, isVisible; protected APropAccessor(String n, ACC acc, boolean expl, boolean nameExpl, boolean ignore, boolean visible) { name = n; accessor = acc; isExplicit = expl; isNameExplicit = nameExpl; isToIgnore = ignore; isVisible = visible; } // We saw `@JsonIgnore` and that's all we need public static <T extends AccessibleObject> APropAccessor<T> createIgnorable(String name, T accessor) { return new APropAccessor<T>(name, accessor, false, false, true, false); } // We didn't saw any relevant annotation public static <T extends AccessibleObject> APropAccessor<T> createImplicit(String name, T accessor, boolean visible) { return new APropAccessor<T>(name, accessor, false, false, false, visible); } // We only saw "empty" `@JsonProperty` (or similar marker) public static <T extends AccessibleObject> APropAccessor<T> createVisible(String name, T accessor) { return new APropAccessor<T>(name, accessor, true, false, false, true); } // We saw `@JsonProperty` with non-empty name public static <T extends AccessibleObject> APropAccessor<T> createExplicit(String name, T accessor) { return new APropAccessor<T>(name, accessor, true, true, false, true); } } }