| package com.fasterxml.jackson.databind.ser; |
| |
| import com.fasterxml.jackson.annotation.JsonInclude; |
| import com.fasterxml.jackson.databind.*; |
| import com.fasterxml.jackson.databind.annotation.JsonSerialize; |
| import com.fasterxml.jackson.databind.introspect.*; |
| import com.fasterxml.jackson.databind.jsontype.TypeSerializer; |
| import com.fasterxml.jackson.databind.util.*; |
| |
| /** |
| * Helper class for {@link BeanSerializerFactory} that is used to |
| * construct {@link BeanPropertyWriter} instances. Can be sub-classed |
| * to change behavior. |
| */ |
| public class PropertyBuilder |
| { |
| // @since 2.7 |
| private final static Object NO_DEFAULT_MARKER = Boolean.FALSE; |
| |
| final protected SerializationConfig _config; |
| final protected BeanDescription _beanDesc; |
| |
| final protected AnnotationIntrospector _annotationIntrospector; |
| |
| /** |
| * If a property has serialization inclusion value of |
| * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}, |
| * we may need to know the default value of the bean, to know if property value |
| * equals default one. |
| *<p> |
| * NOTE: only used if enclosing class defines NON_DEFAULT, but NOT if it is the |
| * global default OR per-property override. |
| */ |
| protected Object _defaultBean; |
| |
| /** |
| * Default inclusion mode for properties of the POJO for which |
| * properties are collected; possibly overridden on |
| * per-property basis. Combines global inclusion defaults and |
| * per-type (annotation and type-override) inclusion overrides. |
| */ |
| final protected JsonInclude.Value _defaultInclusion; |
| |
| /** |
| * Marker flag used to indicate that "real" default values are to be used |
| * for properties, as per per-type value inclusion of type <code>NON_DEFAULT</code> |
| * |
| * @since 2.8 |
| */ |
| final protected boolean _useRealPropertyDefaults; |
| |
| public PropertyBuilder(SerializationConfig config, BeanDescription beanDesc) |
| { |
| _config = config; |
| _beanDesc = beanDesc; |
| // 08-Sep-2016, tatu: This gets tricky, with 3 levels of definitions: |
| // (a) global default inclusion |
| // (b) per-type default inclusion (from annotation or config overrides; |
| // latter having precedence |
| // Cc) per-property override |
| // |
| // and not only requiring merging, but also considering special handling |
| // for NON_DEFAULT in case of (b) (vs (a) or (c)) |
| JsonInclude.Value inclPerType = JsonInclude.Value.merge( |
| beanDesc.findPropertyInclusion(JsonInclude.Value.empty()), |
| config.getDefaultPropertyInclusion(beanDesc.getBeanClass(), |
| JsonInclude.Value.empty())); |
| _defaultInclusion = JsonInclude.Value.merge(config.getDefaultPropertyInclusion(), |
| inclPerType); |
| _useRealPropertyDefaults = inclPerType.getValueInclusion() == JsonInclude.Include.NON_DEFAULT; |
| _annotationIntrospector = _config.getAnnotationIntrospector(); |
| } |
| |
| /* |
| /********************************************************** |
| /* Public API |
| /********************************************************** |
| */ |
| |
| public Annotations getClassAnnotations() { |
| return _beanDesc.getClassAnnotations(); |
| } |
| |
| /** |
| * @param contentTypeSer Optional explicit type information serializer |
| * to use for contained values (only used for properties that are |
| * of container type) |
| */ |
| @SuppressWarnings("deprecation") |
| protected BeanPropertyWriter buildWriter(SerializerProvider prov, |
| BeanPropertyDefinition propDef, JavaType declaredType, JsonSerializer<?> ser, |
| TypeSerializer typeSer, TypeSerializer contentTypeSer, |
| AnnotatedMember am, boolean defaultUseStaticTyping) |
| throws JsonMappingException |
| { |
| // do we have annotation that forces type to use (to declared type or its super type)? |
| JavaType serializationType; |
| try { |
| serializationType = findSerializationType(am, defaultUseStaticTyping, declaredType); |
| } catch (JsonMappingException e) { |
| return prov.reportBadPropertyDefinition(_beanDesc, propDef, e.getMessage()); |
| } |
| |
| // Container types can have separate type serializers for content (value / element) type |
| if (contentTypeSer != null) { |
| /* 04-Feb-2010, tatu: Let's force static typing for collection, if there is |
| * type information for contents. Should work well (for JAXB case); can be |
| * revisited if this causes problems. |
| */ |
| if (serializationType == null) { |
| // serializationType = TypeFactory.type(am.getGenericType(), _beanDesc.getType()); |
| serializationType = declaredType; |
| } |
| JavaType ct = serializationType.getContentType(); |
| // Not exactly sure why, but this used to occur; better check explicitly: |
| if (ct == null) { |
| prov.reportBadPropertyDefinition(_beanDesc, propDef, |
| "serialization type "+serializationType+" has no content"); |
| } |
| serializationType = serializationType.withContentTypeHandler(contentTypeSer); |
| ct = serializationType.getContentType(); |
| } |
| |
| Object valueToSuppress = null; |
| boolean suppressNulls = false; |
| |
| // 12-Jul-2016, tatu: [databind#1256] Need to make sure we consider type refinement |
| JavaType actualType = (serializationType == null) ? declaredType : serializationType; |
| |
| // 17-Aug-2016, tatu: Default inclusion covers global default (for all types), as well |
| // as type-default for enclosing POJO. What we need, then, is per-type default (if any) |
| // for declared property type... and finally property annotation overrides |
| JsonInclude.Value inclV = _config.getDefaultPropertyInclusion(actualType.getRawClass(), |
| _defaultInclusion); |
| |
| // property annotation override |
| |
| inclV = inclV.withOverrides(propDef.findInclusion()); |
| JsonInclude.Include inclusion = inclV.getValueInclusion(); |
| |
| if (inclusion == JsonInclude.Include.USE_DEFAULTS) { // should not occur but... |
| inclusion = JsonInclude.Include.ALWAYS; |
| } |
| |
| switch (inclusion) { |
| case NON_DEFAULT: |
| // 11-Nov-2015, tatu: This is tricky because semantics differ between cases, |
| // so that if enclosing class has this, we may need to access values of property, |
| // whereas for global defaults OR per-property overrides, we have more |
| // static definition. Sigh. |
| // First: case of class/type specifying it; try to find POJO property defaults |
| Object defaultBean; |
| |
| // 16-Oct-2016, tatu: Note: if we can not for some reason create "default instance", |
| // revert logic to the case of general/per-property handling, so both |
| // type-default AND null are to be excluded. |
| // (as per [databind#1417] |
| if (_useRealPropertyDefaults && (defaultBean = getDefaultBean()) != null) { |
| // 07-Sep-2016, tatu: may also need to front-load access forcing now |
| if (prov.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) { |
| am.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); |
| } |
| try { |
| valueToSuppress = am.getValue(defaultBean); |
| } catch (Exception e) { |
| _throwWrapped(e, propDef.getName(), defaultBean); |
| } |
| } else { |
| valueToSuppress = getDefaultValue(actualType); |
| suppressNulls = true; |
| } |
| if (valueToSuppress == null) { |
| suppressNulls = true; |
| } else { |
| if (valueToSuppress.getClass().isArray()) { |
| valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress); |
| } |
| } |
| break; |
| case NON_ABSENT: // new with 2.6, to support Guava/JDK8 Optionals |
| // always suppress nulls |
| suppressNulls = true; |
| // and for referential types, also "empty", which in their case means "absent" |
| if (actualType.isReferenceType()) { |
| valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY; |
| } |
| break; |
| case NON_EMPTY: |
| // always suppress nulls |
| suppressNulls = true; |
| // but possibly also 'empty' values: |
| valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY; |
| break; |
| case NON_NULL: |
| suppressNulls = true; |
| // fall through |
| case ALWAYS: // default |
| default: |
| // we may still want to suppress empty collections, as per [JACKSON-254]: |
| if (actualType.isContainerType() |
| && !_config.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS)) { |
| valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY; |
| } |
| break; |
| } |
| BeanPropertyWriter bpw = new BeanPropertyWriter(propDef, |
| am, _beanDesc.getClassAnnotations(), declaredType, |
| ser, typeSer, serializationType, suppressNulls, valueToSuppress); |
| |
| // How about custom null serializer? |
| Object serDef = _annotationIntrospector.findNullSerializer(am); |
| if (serDef != null) { |
| bpw.assignNullSerializer(prov.serializerInstance(am, serDef)); |
| } |
| // And then, handling of unwrapping |
| NameTransformer unwrapper = _annotationIntrospector.findUnwrappingNameTransformer(am); |
| if (unwrapper != null) { |
| bpw = bpw.unwrappingWriter(unwrapper); |
| } |
| return bpw; |
| } |
| |
| /* |
| /********************************************************** |
| /* Helper methods; annotation access |
| /********************************************************** |
| */ |
| |
| /** |
| * Method that will try to determine statically defined type of property |
| * being serialized, based on annotations (for overrides), and alternatively |
| * declared type (if static typing for serialization is enabled). |
| * If neither can be used (no annotations, dynamic typing), returns null. |
| */ |
| protected JavaType findSerializationType(Annotated a, boolean useStaticTyping, JavaType declaredType) |
| throws JsonMappingException |
| { |
| JavaType secondary = _annotationIntrospector.refineSerializationType(_config, a, declaredType); |
| |
| // 11-Oct-2015, tatu: As of 2.7, not 100% sure following checks are needed. But keeping |
| // for now, just in case |
| if (secondary != declaredType) { |
| Class<?> serClass = secondary.getRawClass(); |
| // Must be a super type to be usable |
| Class<?> rawDeclared = declaredType.getRawClass(); |
| if (serClass.isAssignableFrom(rawDeclared)) { |
| ; // fine as is |
| } else { |
| /* 18-Nov-2010, tatu: Related to fixing [JACKSON-416], an issue with such |
| * check is that for deserialization more specific type makes sense; |
| * and for serialization more generic. But alas JAXB uses but a single |
| * annotation to do both... Hence, we must just discard type, as long as |
| * types are related |
| */ |
| if (!rawDeclared.isAssignableFrom(serClass)) { |
| throw new IllegalArgumentException("Illegal concrete-type annotation for method '"+a.getName()+"': class "+serClass.getName()+" not a super-type of (declared) class "+rawDeclared.getName()); |
| } |
| /* 03-Dec-2010, tatu: Actually, ugh, we may need to further relax this |
| * and actually accept subtypes too for serialization. Bit dangerous in theory |
| * but need to trust user here... |
| */ |
| } |
| useStaticTyping = true; |
| declaredType = secondary; |
| } |
| // If using static typing, declared type is known to be the type... |
| JsonSerialize.Typing typing = _annotationIntrospector.findSerializationTyping(a); |
| if ((typing != null) && (typing != JsonSerialize.Typing.DEFAULT_TYPING)) { |
| useStaticTyping = (typing == JsonSerialize.Typing.STATIC); |
| } |
| if (useStaticTyping) { |
| // 11-Oct-2015, tatu: Make sure JavaType also "knows" static-ness... |
| return declaredType.withStaticTyping(); |
| |
| } |
| return null; |
| } |
| |
| /* |
| /********************************************************** |
| /* Helper methods for default value handling |
| /********************************************************** |
| */ |
| |
| protected Object getDefaultBean() |
| { |
| Object def = _defaultBean; |
| if (def == null) { |
| /* If we can fix access rights, we should; otherwise non-public |
| * classes or default constructor will prevent instantiation |
| */ |
| def = _beanDesc.instantiateBean(_config.canOverrideAccessModifiers()); |
| if (def == null) { |
| // 06-Nov-2015, tatu: As per [databind#998], do not fail. |
| /* |
| Class<?> cls = _beanDesc.getClassInfo().getAnnotated(); |
| throw new IllegalArgumentException("Class "+cls.getName()+" has no default constructor; can not instantiate default bean value to support 'properties=JsonSerialize.Inclusion.NON_DEFAULT' annotation"); |
| */ |
| |
| // And use a marker |
| def = NO_DEFAULT_MARKER; |
| } |
| _defaultBean = def; |
| } |
| return (def == NO_DEFAULT_MARKER) ? null : _defaultBean; |
| } |
| |
| /** |
| * Accessor used to find out "default value" for given property, to use for |
| * comparing values to serialize, to determine whether to exclude value from serialization with |
| * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}. |
| * This method is called when we specifically want to know default value within context |
| * of a POJO, when annotation is within containing class, and not for property or |
| * defined as global baseline. |
| *<p> |
| * Note that returning of pseudo-type |
| * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_EMPTY} requires special handling. |
| * |
| * @since 2.7 |
| * @deprecated Since 2.8.5 since this will not allow determining difference between "no default instance" |
| * case and default being `null`. |
| */ |
| @Deprecated // since 2.8.5 |
| protected Object getPropertyDefaultValue(String name, AnnotatedMember member, |
| JavaType type) |
| { |
| Object defaultBean = getDefaultBean(); |
| if (defaultBean == null) { |
| return getDefaultValue(type); |
| } |
| try { |
| return member.getValue(defaultBean); |
| } catch (Exception e) { |
| return _throwWrapped(e, name, defaultBean); |
| } |
| } |
| |
| /** |
| * Accessor used to find out "default value" to use for comparing values to |
| * serialize, to determine whether to exclude value from serialization with |
| * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}. |
| *<p> |
| * Default logic is such that for primitives and wrapper types for primitives, expected |
| * defaults (0 for `int` and `java.lang.Integer`) are returned; for Strings, empty String, |
| * and for structured (Maps, Collections, arrays) and reference types, criteria |
| * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT} |
| * is used. |
| * |
| * @since 2.7 |
| */ |
| protected Object getDefaultValue(JavaType type) |
| { |
| // 06-Nov-2015, tatu: Returning null is fine for Object types; but need special |
| // handling for primitives since they are never passed as nulls. |
| Class<?> cls = type.getRawClass(); |
| |
| Class<?> prim = ClassUtil.primitiveType(cls); |
| if (prim != null) { |
| return ClassUtil.defaultValue(prim); |
| } |
| if (type.isContainerType() || type.isReferenceType()) { |
| return JsonInclude.Include.NON_EMPTY; |
| } |
| if (cls == String.class) { |
| return ""; |
| } |
| return null; |
| } |
| |
| /* |
| /********************************************************** |
| /* Helper methods for exception handling |
| /********************************************************** |
| */ |
| |
| protected Object _throwWrapped(Exception e, String propName, Object defaultBean) |
| { |
| 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 get property '"+propName+"' of default "+defaultBean.getClass().getName()+" instance"); |
| } |
| } |