package com.fasterxml.jackson.databind.introspect; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.*; 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.MapperConfig; import com.fasterxml.jackson.databind.type.TypeBindings; import com.fasterxml.jackson.databind.util.Annotations; /** * Default {@link BeanDescription} implementation. * Can theoretically be subclassed to customize * some aspects of property introspection. */ public class BasicBeanDescription extends BeanDescription { /* /********************************************************** /* General configuration /********************************************************** */ final protected MapperConfig<?> _config; final protected AnnotationIntrospector _annotationIntrospector; /** * Information collected about the class introspected. */ final protected AnnotatedClass _classInfo; /** * We may need type bindings for the bean type. If so, we'll * construct it lazily */ protected TypeBindings _bindings; /* /********************************************************** /* Member information /********************************************************** */ /** * Properties collected for the POJO. */ protected final List<BeanPropertyDefinition> _properties; /** * Details of Object Id to include, if any */ protected ObjectIdInfo _objectIdInfo; // // for deserialization protected AnnotatedMethod _anySetterMethod; protected Map<Object, AnnotatedMember> _injectables; /** * Set of properties that can be ignored during deserialization, due * to being marked as ignored. */ protected Set<String> _ignoredPropertyNames; // // for serialization protected AnnotatedMethod _jsonValueMethod; protected AnnotatedMember _anyGetter; /* /********************************************************** /* Life-cycle /********************************************************** */ protected BasicBeanDescription(MapperConfig<?> config, JavaType type, AnnotatedClass classDef, List<BeanPropertyDefinition> props) { super(type); _config = config; _annotationIntrospector = (config == null) ? null : config.getAnnotationIntrospector(); _classInfo = classDef; _properties = props; } protected BasicBeanDescription(POJOPropertiesCollector coll) { this(coll.getConfig(), coll.getType(), coll.getClassDef(), coll.getProperties()); _objectIdInfo = coll.getObjectIdInfo(); } /** * Factory method to use for constructing an instance to use for building * deserializers. */ public static BasicBeanDescription forDeserialization(POJOPropertiesCollector coll) { BasicBeanDescription desc = new BasicBeanDescription(coll); desc._anySetterMethod = coll.getAnySetterMethod(); desc._ignoredPropertyNames = coll.getIgnoredPropertyNames(); desc._injectables = coll.getInjectables(); desc._jsonValueMethod = coll.getJsonValueMethod(); return desc; } /** * Factory method to use for constructing an instance to use for building * serializers. */ public static BasicBeanDescription forSerialization(POJOPropertiesCollector coll) { BasicBeanDescription desc = new BasicBeanDescription(coll); desc._jsonValueMethod = coll.getJsonValueMethod(); desc._anyGetter = coll.getAnyGetter(); return desc; } /** * 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()); } /* /********************************************************** /* 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; } /* /********************************************************** /* Simple accessors from BeanDescription /********************************************************** */ @Override public AnnotatedClass getClassInfo() { return _classInfo; } @Override public ObjectIdInfo getObjectIdInfo() { return _objectIdInfo; } @Override public List<BeanPropertyDefinition> findProperties() { return _properties; } @Override public AnnotatedMethod findJsonValueMethod() { return _jsonValueMethod; } @Override public Set<String> getIgnoredPropertyNames() { if (_ignoredPropertyNames == null) { return Collections.emptySet(); } return _ignoredPropertyNames; } @Override public boolean hasKnownClassAnnotations() { return _classInfo.hasAnnotations(); } @Override public Annotations getClassAnnotations() { return _classInfo.getAnnotations(); } @Override public TypeBindings bindingsForBeanType() { if (_bindings == null) { _bindings = new TypeBindings(_config.getTypeFactory(), _type); } return _bindings; } @Override public JavaType resolveType(java.lang.reflect.Type jdkType) { if (jdkType == null) { return null; } return bindingsForBeanType().resolveType(jdkType); } @Override public AnnotatedConstructor findDefaultConstructor() { return _classInfo.getDefaultConstructor(); } @Override public AnnotatedMethod findAnySetter() throws IllegalArgumentException { if (_anySetterMethod != 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 = _anySetterMethod.getRawParameterType(0); if (type != String.class && type != Object.class) { throw new IllegalArgumentException("Invalid 'any-setter' annotation on method "+_anySetterMethod.getName()+"(): first argument not of type String or Object, but "+type.getName()); } } return _anySetterMethod; } @Override public Map<Object, AnnotatedMember> findInjectables() { return _injectables; } @Override public List<AnnotatedConstructor> getConstructors() { return _classInfo.getConstructors(); } @Override public Object instantiateBean(boolean fixAccess) { AnnotatedConstructor ac = _classInfo.getDefaultConstructor(); if (ac == null) { return null; } if (fixAccess) { ac.fixAccess(); } try { return ac.getAnnotated().newInstance(); } catch (Exception e) { Throwable t = e; while (t.getCause() != null) { t = t.getCause(); } if (t instanceof Error) throw (Error) t; if (t instanceof RuntimeException) throw (RuntimeException) t; throw new IllegalArgumentException("Failed to instantiate bean of type "+_classInfo.getAnnotated().getName()+": ("+t.getClass().getName()+") "+t.getMessage(), t); } } /* /********************************************************** /* Simple accessors, extended /********************************************************** */ @Override public AnnotatedMethod findMethod(String name, Class<?>[] paramTypes) { return _classInfo.findMethod(name, paramTypes); } /* /********************************************************** /* General per-class annotation introspection /********************************************************** */ @Override public JsonFormat.Value findExpectedFormat(JsonFormat.Value defValue) { if (_annotationIntrospector != null) { JsonFormat.Value v = _annotationIntrospector.findFormat(_classInfo); if (v != null) { return v; } } return defValue; } /* /********************************************************** /* Introspection for serialization, factories /********************************************************** */ @Override public List<AnnotatedMethod> getFactoryMethods() { // must filter out anything that clearly is not a factory method List<AnnotatedMethod> candidates = _classInfo.getStaticMethods(); if (candidates.isEmpty()) { return candidates; } ArrayList<AnnotatedMethod> result = new ArrayList<AnnotatedMethod>(); for (AnnotatedMethod am : candidates) { if (isFactoryMethod(am)) { result.add(am); } } return result; } @Override public Constructor<?> findSingleArgConstructor(Class<?>... argTypes) { for (AnnotatedConstructor ac : _classInfo.getConstructors()) { // This list is already filtered to only include accessible /* (note: for now this is a redundant check; but in future * that may change; thus leaving here for now) */ if (ac.getParameterCount() == 1) { Class<?> actArg = ac.getRawParameterType(0); for (Class<?> expArg : argTypes) { if (expArg == actArg) { return ac.getAnnotated(); } } } } return null; } @Override public Method findFactoryMethod(Class<?>... expArgTypes) { // So, of all single-arg static methods: for (AnnotatedMethod am : _classInfo.getStaticMethods()) { if (isFactoryMethod(am)) { // And must take one of expected arg types (or supertype) Class<?> actualArgType = am.getRawParameterType(0); for (Class<?> expArgType : expArgTypes) { // And one that matches what we would pass in if (actualArgType.isAssignableFrom(expArgType)) { return am.getAnnotated(); } } } } return null; } 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 * (a) "valueOf" (at this point, need not be public) */ if (_annotationIntrospector.hasCreatorAnnotation(am)) { return true; } if ("valueOf".equals(am.getName())) { return true; } return false; } /** * Method for getting ordered list of named Creator properties. * Returns an empty list is none found. If multiple Creator * methods are defined, order between properties from different * methods is undefined; however, properties for each such * Creator are ordered properly relative to each other. For the * usual case of just a single Creator, named properties are * thus properly ordered. */ public List<String> findCreatorPropertyNames() { List<String> names = null; for (int i = 0; i < 2; ++i) { List<? extends AnnotatedWithParams> l = (i == 0) ? getConstructors() : getFactoryMethods(); for (AnnotatedWithParams creator : l) { int argCount = creator.getParameterCount(); if (argCount < 1) continue; PropertyName name = _annotationIntrospector.findNameForDeserialization(creator.getParameter(0)); if (name == null) { continue; } if (names == null) { names = new ArrayList<String>(); } names.add(name.getSimpleName()); for (int p = 1; p < argCount; ++p) { name = _annotationIntrospector.findNameForDeserialization(creator.getParameter(p)); names.add((name == null) ? null : name.getSimpleName()); } } } if (names == null) { return Collections.emptyList(); } return names; } /* /********************************************************** /* Introspection for serialization, other /********************************************************** */ /** * 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.Include findSerializationInclusion(JsonInclude.Include defValue) { if (_annotationIntrospector == null) { return defValue; } return _annotationIntrospector.findSerializationInclusion(_classInfo, 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 (_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("Invalid 'any-getter' annotation on method "+_anyGetter.getName()+"(): return type is not instance of java.util.Map"); } } return _anyGetter; } @Override public Map<String,AnnotatedMember> findBackReferenceProperties() { HashMap<String,AnnotatedMember> result = null; for (BeanPropertyDefinition property : _properties) { AnnotatedMember am = property.getMutator(); if (am == null) { continue; } AnnotationIntrospector.ReferenceProperty refDef = _annotationIntrospector.findReferenceType(am); if (refDef != null && refDef.isBackReference()) { if (result == null) { result = new HashMap<String,AnnotatedMember>(); } String refName = refDef.getName(); if (result.put(refName, am) != null) { throw new IllegalArgumentException("Multiple back-reference properties with name '"+refName+"'"); } } } return result; } @Override public Class<?> findPOJOBuilder() { return (_annotationIntrospector == null) ? null : _annotationIntrospector.findPOJOBuilder(_classInfo); } @Override public JsonPOJOBuilder.Value findPOJOBuilderConfig() { return (_annotationIntrospector == null) ? null : _annotationIntrospector.findPOJOBuilderConfig(_classInfo); } /* /********************************************************** /* Helper methods for field introspection /********************************************************** */ /** * @param ignoredProperties (optional) names of properties to ignore; * any fields that would be recognized as one of these properties * is ignored. * @param forSerialization If true, will collect serializable property * fields; if false, deserializable * * @return Ordered Map with logical property name as key, and * matching field as value. */ public LinkedHashMap<String,AnnotatedField> _findPropertyFields( Collection<String> ignoredProperties, boolean forSerialization) { LinkedHashMap<String,AnnotatedField> results = new LinkedHashMap<String,AnnotatedField>(); for (BeanPropertyDefinition property : _properties) { AnnotatedField f = property.getField(); if (f != null) { String name = property.getName(); if (ignoredProperties != null) { if (ignoredProperties.contains(name)) { continue; } } results.put(name, f); } } return results; } }