diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index 3bcff6ec33..b0ae9b553a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -1,14 +1,8 @@ package com.fasterxml.jackson.databind; -import java.lang.annotation.Annotation; -import java.util.*; - import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.core.Versioned; - -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.cfg.MapperConfig; @@ -19,6 +13,12 @@ import com.fasterxml.jackson.databind.util.Converter; import com.fasterxml.jackson.databind.util.NameTransformer; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + /** * Abstract class that defines API used for introspecting annotation-based * configuration for serialization and deserialization. Separated @@ -719,6 +719,15 @@ public String[] findEnumValues(Class enumType, Enum[] enumValues, String[] return names; } + /** + * Method to find any aliases on enum-constants in the given class. + * @param enumType - Class definition to search. + * @return Returns a Map with enum value as key and found aliases as value. + */ + public Map findEnumAliases(Class enumType) { + return null; + } + /** * Finds the Enum value that should be considered the default value, if possible. * diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index aa1e20619a..e3afb2620d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1,15 +1,9 @@ package com.fasterxml.jackson.databind.deser; -import java.io.Serializable; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; - import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonCreator.Mode; - +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig; import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; @@ -28,6 +22,13 @@ import com.fasterxml.jackson.databind.type.*; import com.fasterxml.jackson.databind.util.*; +import java.io.Serializable; +import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReference; + /** * Abstract factory base class that can provide deserializers for standard * JDK classes, including collection classes and simple heuristics for @@ -1394,7 +1395,7 @@ public JsonDeserializer createEnumDeserializer(DeserializationContext ctxt, } } } - + // Need to consider @JsonValue if one found if (deser == null) { deser = new EnumDeserializer(constructEnumResolver(enumClass, @@ -1409,6 +1410,9 @@ public JsonDeserializer createEnumDeserializer(DeserializationContext ctxt, deser = mod.modifyEnumDeserializer(config, type, beanDesc, deser); } } + + boolean derp = beanDesc.hasKnownClassAnnotations(); + return deser; } @@ -1562,6 +1566,7 @@ private KeyDeserializer _createEnumKeyDeserializer(DeserializationContext ctxt, return StdKeyDeserializers.constructDelegatingKeyDeserializer(config, type, valueDesForKey); } } + EnumResolver enumRes = constructEnumResolver(enumClass, config, beanDesc.findJsonValueAccessor()); // May have @JsonCreator for static factory method: for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java index d1b5648ac5..7029c05649 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java @@ -1,11 +1,8 @@ package com.fasterxml.jackson.databind.deser.std; -import java.io.IOException; - import com.fasterxml.jackson.annotation.JsonFormat; - -import com.fasterxml.jackson.core.*; - +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; @@ -15,6 +12,8 @@ import com.fasterxml.jackson.databind.util.CompactStringObjectMap; import com.fasterxml.jackson.databind.util.EnumResolver; +import java.io.IOException; + /** * Deserializer class that can deserialize instances of * specified Enum class from Strings and Integers. @@ -58,7 +57,7 @@ protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive) /** * Factory method used when Enum instances are to be deserialized * using a creator (static factory method) - * + * * @return Deserializer based on given factory method */ public static JsonDeserializer deserializerForCreator(DeserializationConfig config, @@ -77,7 +76,7 @@ public static JsonDeserializer deserializerForCreator(DeserializationConfig c /** * Factory method used when Enum instances are to be deserialized * using a zero-/no-args factory method - * + * * @return Deserializer based on given no-args factory method */ public static JsonDeserializer deserializerForNoArgsCreator(DeserializationConfig config, @@ -96,7 +95,7 @@ public EnumDeserializer withResolved(Boolean caseInsensitive) { } return new EnumDeserializer(this, caseInsensitive); } - + @Override public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException @@ -126,7 +125,8 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonToken curr = p.currentToken(); - + + // Usually should just get string value: if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) { CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING) @@ -169,10 +169,11 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx /* Internal helper methods /********************************************************** */ - + private final Object _deserializeAltString(JsonParser p, DeserializationContext ctxt, CompactStringObjectMap lookup, String name) throws IOException { + name = name.trim(); if (name.length() == 0) { if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) { diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index 27b2910281..a73550be39 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -1,15 +1,7 @@ package com.fasterxml.jackson.databind.introspect; -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.MalformedParametersException; -import java.lang.reflect.Parameter; -import java.util.*; - import com.fasterxml.jackson.annotation.*; - import com.fasterxml.jackson.core.Version; - import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.*; import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; @@ -24,50 +16,57 @@ import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.util.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.MalformedParametersException; +import java.lang.reflect.Parameter; +import java.util.*; + /** * {@link AnnotationIntrospector} implementation that handles standard * Jackson annotations. */ public class JacksonAnnotationIntrospector - extends AnnotationIntrospector - implements java.io.Serializable -{ + extends AnnotationIntrospector + implements java.io.Serializable { private static final long serialVersionUID = 1L; @SuppressWarnings("unchecked") private final static Class[] ANNOTATIONS_TO_INFER_SER = (Class[]) - new Class[] { - JsonSerialize.class, - JsonView.class, - JsonFormat.class, - JsonTypeInfo.class, - JsonRawValue.class, - JsonUnwrapped.class, - JsonBackReference.class, - JsonManagedReference.class - }; + new Class[]{ + JsonSerialize.class, + JsonView.class, + JsonFormat.class, + JsonTypeInfo.class, + JsonRawValue.class, + JsonUnwrapped.class, + JsonBackReference.class, + JsonManagedReference.class + }; @SuppressWarnings("unchecked") private final static Class[] ANNOTATIONS_TO_INFER_DESER = (Class[]) - new Class[] { - JsonDeserialize.class, - JsonView.class, - JsonFormat.class, - JsonTypeInfo.class, - JsonUnwrapped.class, - JsonBackReference.class, - JsonManagedReference.class, - JsonMerge.class // since 2.9 - }; + new Class[]{ + JsonDeserialize.class, + JsonView.class, + JsonFormat.class, + JsonTypeInfo.class, + JsonUnwrapped.class, + JsonBackReference.class, + JsonManagedReference.class, + JsonMerge.class // since 2.9 + }; // NOTE: To avoid mandatory Module dependency to "java.beans", support for 2 // annotations is done dynamically. private static final JavaBeansAnnotations _javaBeansHelper; + static { JavaBeansAnnotations x = null; try { x = JavaBeansAnnotations.instance(); - } catch (Throwable t) { } + } catch (Throwable t) { + } _javaBeansHelper = x; } @@ -75,10 +74,10 @@ public class JacksonAnnotationIntrospector * Since introspection of annotation types is a performance issue in some * use cases (rare, but do exist), let's try a simple cache to reduce * need for actual meta-annotation introspection. - *

+ *

* Non-final only because it needs to be re-created after deserialization. */ - protected transient LookupCache,Boolean> _annotationsInside = new SimpleLookupCache,Boolean>(48, 96); + protected transient LookupCache, Boolean> _annotationsInside = new SimpleLookupCache, Boolean>(48, 96); /* /********************************************************************** @@ -89,7 +88,7 @@ public class JacksonAnnotationIntrospector /** * See {@link #setConstructorPropertiesImpliesCreator(boolean)} for * explanation. - *

+ *

* Defaults to true. */ protected boolean _cfgConstructorPropertiesImpliesCreator = true; @@ -100,7 +99,8 @@ public class JacksonAnnotationIntrospector /********************************************************************** */ - public JacksonAnnotationIntrospector() { } + public JacksonAnnotationIntrospector() { + } @Override public Version version() { @@ -109,7 +109,7 @@ public Version version() { protected Object readResolve() { if (_annotationsInside == null) { - _annotationsInside = new SimpleLookupCache,Boolean>(48, 48); + _annotationsInside = new SimpleLookupCache, Boolean>(48, 48); } return this; } @@ -125,11 +125,10 @@ protected Object readResolve() { * if set to `true`, existence DOES indicate that the given constructor should * be considered a creator; `false` that it should NOT be considered a creator * without explicit use of JsonCreator annotation. - *

+ *

* Default setting is `true` */ - public JacksonAnnotationIntrospector setConstructorPropertiesImpliesCreator(boolean b) - { + public JacksonAnnotationIntrospector setConstructorPropertiesImpliesCreator(boolean b) { _cfgConstructorPropertiesImpliesCreator = b; return this; } @@ -167,7 +166,7 @@ public boolean isAnnotationBundle(Annotation ann) { @Override // since 2.7 public String[] findEnumValues(Class enumType, Enum[] enumValues, String[] names) { - HashMap expl = null; + HashMap expl = null; for (Field f : ClassUtil.getDeclaredFields(enumType)) { if (!f.isEnumConstant()) { continue; @@ -181,7 +180,7 @@ public String[] findEnumValues(Class enumType, Enum[] enumValues, String[] continue; } if (expl == null) { - expl = new HashMap(); + expl = new HashMap(); } expl.put(f.getName(), n); } @@ -198,6 +197,25 @@ public String[] findEnumValues(Class enumType, Enum[] enumValues, String[] return names; } + /** + * @param enumType - Class definition to search. + * @return Returns a Map with enum value as key and found aliases as value. + */ + public Map findEnumAliases(Class enumType) { + HashMap aliasMap = new HashMap<>(); + for (Field f : ClassUtil.getDeclaredFields(enumType)) { + if (!f.isEnumConstant()) { + continue; + } + JsonAlias aliasAnnotation = f.getAnnotation(JsonAlias.class); + if(aliasAnnotation != null){ + String[] aliases = aliasAnnotation.value(); + aliasMap.put(f.getName(), aliases); + } + } + return aliasMap; + } + /** * Finds the Enum value that should be considered the default value, if possible. *

@@ -219,8 +237,7 @@ public Enum findDefaultEnumValue(Class> enumCls) { */ @Override - public PropertyName findRootName(AnnotatedClass ac) - { + public PropertyName findRootName(AnnotatedClass ac) { JsonRootName ann = _findAnnotation(ac, JsonRootName.class); if (ann == null) { return null; @@ -233,8 +250,7 @@ public PropertyName findRootName(AnnotatedClass ac) } @Override // since 2.8 - public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated a) - { + public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated a) { JsonIgnoreProperties v = _findAnnotation(a, JsonIgnoreProperties.class); if (v == null) { return JsonIgnoreProperties.Value.empty(); @@ -247,7 +263,7 @@ public Boolean isIgnorableType(AnnotatedClass ac) { JsonIgnoreType ignore = _findAnnotation(ac, JsonIgnoreType.class); return (ignore == null) ? null : ignore.value(); } - + @Override public Object findFilterId(Annotated a) { JsonFilter ann = _findAnnotation(a, JsonFilter.class); @@ -262,8 +278,7 @@ public Object findFilterId(Annotated a) { } @Override - public Object findNamingStrategy(AnnotatedClass ac) - { + public Object findNamingStrategy(AnnotatedClass ac) { JsonNaming ann = _findAnnotation(ac, JsonNaming.class); return (ann == null) ? null : ann.value(); } @@ -282,8 +297,7 @@ public String findClassDescription(AnnotatedClass ac) { @Override public VisibilityChecker findAutoDetectVisibility(MapperConfig config, - AnnotatedClass ac, VisibilityChecker checker) - { + AnnotatedClass ac, VisibilityChecker checker) { JsonAutoDetect ann = _findAnnotation(ac, JsonAutoDetect.class); if (ann == null) { return checker; @@ -298,8 +312,7 @@ public VisibilityChecker findAutoDetectVisibility(MapperConfig config, */ @Override - public String findImplicitPropertyName(AnnotatedMember m) - { + public String findImplicitPropertyName(AnnotatedMember m) { // Always get name for fields so why not if (m instanceof AnnotatedField) { return m.getName(); @@ -328,8 +341,7 @@ public String findImplicitPropertyName(AnnotatedMember m) return null; } - protected String _findImplicitName(AnnotatedWithParams m, int index) - { + protected String _findImplicitName(AnnotatedWithParams m, int index) { try { Parameter[] params = m.getNativeParameters(); Parameter p = params[index]; @@ -368,8 +380,7 @@ public boolean hasIgnoreMarker(AnnotatedMember m) { } @Override - public Boolean hasRequiredMarker(AnnotatedMember m) - { + public Boolean hasRequiredMarker(AnnotatedMember m) { JsonProperty ann = _findAnnotation(m, JsonProperty.class); if (ann != null) { return ann.required(); @@ -403,7 +414,7 @@ public Integer findPropertyIndex(Annotated ann) { } return null; } - + @Override public String findPropertyDefaultValue(Annotated ann) { JsonProperty prop = _findAnnotation(ann, JsonProperty.class); @@ -414,16 +425,15 @@ public String findPropertyDefaultValue(Annotated ann) { // Since annotations do not allow nulls, need to assume empty means "none" return str.isEmpty() ? null : str; } - + @Override public JsonFormat.Value findFormat(Annotated ann) { JsonFormat f = _findAnnotation(ann, JsonFormat.class); - return (f == null) ? null : new JsonFormat.Value(f); + return (f == null) ? null : new JsonFormat.Value(f); } - @Override - public ReferenceProperty findReferenceType(AnnotatedMember member) - { + @Override + public ReferenceProperty findReferenceType(AnnotatedMember member) { JsonManagedReference ref1 = _findAnnotation(member, JsonManagedReference.class); if (ref1 != null) { return AnnotationIntrospector.ReferenceProperty.managed(ref1.value()); @@ -436,8 +446,7 @@ public ReferenceProperty findReferenceType(AnnotatedMember member) } @Override - public NameTransformer findUnwrappingNameTransformer(AnnotatedMember member) - { + public NameTransformer findUnwrappingNameTransformer(AnnotatedMember member) { JsonUnwrapped ann = _findAnnotation(member, JsonUnwrapped.class); // if not enabled, just means annotation is not enabled; not necessarily // that unwrapping should not be done (relevant when using chained introspectors) @@ -476,19 +485,17 @@ public JacksonInject.Value findInjectableValue(AnnotatedMember m) { } @Override - public Class[] findViews(Annotated a) - { + public Class[] findViews(Annotated a) { JsonView ann = _findAnnotation(a, JsonView.class); return (ann == null) ? null : ann.value(); } @Override public AnnotatedMethod resolveSetterConflict(MapperConfig config, - AnnotatedMethod setter1, AnnotatedMethod setter2) - { + AnnotatedMethod setter1, AnnotatedMethod setter2) { Class cls1 = setter1.getRawParameterType(0); Class cls2 = setter2.getRawParameterType(0); - + // First: prefer primitives over non-primitives // 11-Dec-2015, tatu: TODO, perhaps consider wrappers for primitives too? if (cls1.isPrimitive()) { @@ -498,7 +505,7 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig config, } else if (cls2.isPrimitive()) { return setter2; } - + if (cls1 == String.class) { if (cls2 != String.class) { return setter1; @@ -518,15 +525,14 @@ public AnnotatedMethod resolveSetterConflict(MapperConfig config, @Override public JsonTypeInfo.Value findPolymorphicTypeInfo(MapperConfig config, - Annotated ann) - { + Annotated ann) { JsonTypeInfo t = _findAnnotation(ann, JsonTypeInfo.class); return (t == null) ? null : JsonTypeInfo.Value.from(t); } @Override public Object findTypeResolverBuilder(MapperConfig config, - Annotated ann) { + Annotated ann) { JsonTypeResolver a = _findAnnotation(ann, JsonTypeResolver.class); return (a == null) ? a : a.value(); } @@ -567,8 +573,7 @@ public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig co */ @Override - public List findSubtypes(MapperConfig config, Annotated a) - { + public List findSubtypes(MapperConfig config, Annotated a) { JsonSubTypes t = _findAnnotation(a, JsonSubTypes.class); if (t == null) return null; JsonSubTypes.Type[] types = t.value(); @@ -579,9 +584,8 @@ public List findSubtypes(MapperConfig config, Annotated a) return result; } - @Override - public String findTypeName(MapperConfig config, AnnotatedClass ac) - { + @Override + public String findTypeName(MapperConfig config, AnnotatedClass ac) { JsonTypeName tn = _findAnnotation(ac, JsonTypeName.class); return (tn == null) ? null : tn.value(); } @@ -609,8 +613,8 @@ public ObjectIdInfo findObjectIdInfo(MapperConfig config, Annotated ann) { } @Override - public ObjectIdInfo findObjectReferenceInfo(MapperConfig config, - Annotated ann, ObjectIdInfo objectIdInfo) { + public ObjectIdInfo findObjectReferenceInfo(MapperConfig config, + Annotated ann, ObjectIdInfo objectIdInfo) { JsonIdentityReference ref = _findAnnotation(ann, JsonIdentityReference.class); if (ref == null) { return objectIdInfo; @@ -628,8 +632,7 @@ public ObjectIdInfo findObjectReferenceInfo(MapperConfig config, */ @Override - public Object findSerializer(MapperConfig config, Annotated a) - { + public Object findSerializer(MapperConfig config, Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); if (ann != null) { @SuppressWarnings("rawtypes") @@ -638,23 +641,22 @@ public Object findSerializer(MapperConfig config, Annotated a) return serClass; } } - + /* 18-Oct-2010, tatu: [JACKSON-351] @JsonRawValue handled just here, for now; * if we need to get raw indicator from other sources need to add * separate accessor within {@link AnnotationIntrospector} interface. */ - JsonRawValue annRaw = _findAnnotation(a, JsonRawValue.class); + JsonRawValue annRaw = _findAnnotation(a, JsonRawValue.class); if ((annRaw != null) && annRaw.value()) { // let's construct instance with nominal type: Class cls = a.getRawType(); return new RawSerializer(cls); - } + } return null; } @Override - public Object findKeySerializer(MapperConfig config, Annotated a) - { + public Object findKeySerializer(MapperConfig config, Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); if (ann != null) { @SuppressWarnings("rawtypes") @@ -667,8 +669,7 @@ public Object findKeySerializer(MapperConfig config, Annotated a) } @Override - public Object findContentSerializer(MapperConfig config, Annotated a) - { + public Object findContentSerializer(MapperConfig config, Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); if (ann != null) { @SuppressWarnings("rawtypes") @@ -681,8 +682,7 @@ public Object findContentSerializer(MapperConfig config, Annotated a) } @Override - public Object findNullSerializer(MapperConfig config, Annotated a) - { + public Object findNullSerializer(MapperConfig config, Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); if (ann != null) { @SuppressWarnings("rawtypes") @@ -695,16 +695,14 @@ public Object findNullSerializer(MapperConfig config, Annotated a) } @Override - public JsonInclude.Value findPropertyInclusion(MapperConfig config, Annotated a) - { + public JsonInclude.Value findPropertyInclusion(MapperConfig config, Annotated a) { JsonInclude inc = _findAnnotation(a, JsonInclude.class); JsonInclude.Value value = (inc == null) ? JsonInclude.Value.empty() : JsonInclude.Value.from(inc); return value; } @Override - public JsonSerialize.Typing findSerializationTyping(MapperConfig config, Annotated a) - { + public JsonSerialize.Typing findSerializationTyping(MapperConfig config, Annotated a) { JsonSerialize ann = _findAnnotation(a, JsonSerialize.class); return (ann == null) ? null : ann.typing(); } @@ -729,13 +727,12 @@ public Object findSerializationContentConverter(MapperConfig config, Annotate @Override public JavaType refineSerializationType(final MapperConfig config, - final Annotated a, final JavaType baseType) throws JsonMappingException - { + final Annotated a, final JavaType baseType) throws JsonMappingException { JavaType type = baseType; final TypeFactory tf = config.getTypeFactory(); final JsonSerialize jsonSer = _findAnnotation(a, JsonSerialize.class); - + // Ok: start by refining the main type itself; common to all types final Class serClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.as()); @@ -765,7 +762,7 @@ public JavaType refineSerializationType(final MapperConfig config, throw new JsonMappingException(null, String.format("Failed to widen type %s with annotation (value %s), from '%s': %s", type, serClass.getName(), a.getName(), iae.getMessage()), - iae); + iae); } } } @@ -800,7 +797,7 @@ public JavaType refineSerializationType(final MapperConfig config, throw new JsonMappingException(null, String.format("Failed to widen key type of %s with concrete-type annotation (value %s), from '%s': %s", type, keyClass.getName(), a.getName(), iae.getMessage()), - iae); + iae); } } type = ((MapLikeType) type).withKeyType(keyType); @@ -810,37 +807,37 @@ public JavaType refineSerializationType(final MapperConfig config, JavaType contentType = type.getContentType(); if (contentType != null) { // collection[like], map[like], array, reference // And then value types for all containers: - final Class contentClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.contentAs()); - if (contentClass != null) { - if (contentType.hasRawClass(contentClass)) { - contentType = contentType.withStaticTyping(); - } else { - // 03-Apr-2016, tatu: As per [databind#1178], may need to actually - // specialize (narrow) type sometimes, even if more commonly opposite - // is needed. - Class currRaw = contentType.getRawClass(); - try { - if (contentClass.isAssignableFrom(currRaw)) { // common case - contentType = tf.constructGeneralizedType(contentType, contentClass); - } else if (currRaw.isAssignableFrom(contentClass)) { // specialization, ok as well - contentType = tf.constructSpecializedType(contentType, contentClass); - } else if (_primitiveAndWrapper(currRaw, contentClass)) { - // 27-Apr-2017, tatu: [databind#1592] ignore primitive<->wrapper refinements - contentType = contentType.withStaticTyping(); - } else { - throw new JsonMappingException(null, - String.format("Cannot refine serialization content type %s into %s; types not related", - contentType, contentClass.getName())); - } - } catch (IllegalArgumentException iae) { // shouldn't really happen - throw new JsonMappingException(null, - String.format("Internal error: failed to refine value type of %s with concrete-type annotation (value %s), from '%s': %s", - type, contentClass.getName(), a.getName(), iae.getMessage()), - iae); - } - } - type = type.withContentType(contentType); - } + final Class contentClass = (jsonSer == null) ? null : _classIfExplicit(jsonSer.contentAs()); + if (contentClass != null) { + if (contentType.hasRawClass(contentClass)) { + contentType = contentType.withStaticTyping(); + } else { + // 03-Apr-2016, tatu: As per [databind#1178], may need to actually + // specialize (narrow) type sometimes, even if more commonly opposite + // is needed. + Class currRaw = contentType.getRawClass(); + try { + if (contentClass.isAssignableFrom(currRaw)) { // common case + contentType = tf.constructGeneralizedType(contentType, contentClass); + } else if (currRaw.isAssignableFrom(contentClass)) { // specialization, ok as well + contentType = tf.constructSpecializedType(contentType, contentClass); + } else if (_primitiveAndWrapper(currRaw, contentClass)) { + // 27-Apr-2017, tatu: [databind#1592] ignore primitive<->wrapper refinements + contentType = contentType.withStaticTyping(); + } else { + throw new JsonMappingException(null, + String.format("Cannot refine serialization content type %s into %s; types not related", + contentType, contentClass.getName())); + } + } catch (IllegalArgumentException iae) { // shouldn't really happen + throw new JsonMappingException(null, + String.format("Internal error: failed to refine value type of %s with concrete-type annotation (value %s), from '%s': %s", + type, contentClass.getName(), a.getName(), iae.getMessage()), + iae); + } + } + type = type.withContentType(contentType); + } } return type; } @@ -874,7 +871,7 @@ private final Boolean _findSortAlpha(Annotated ann) { @Override public void findAndAddVirtualProperties(MapperConfig config, AnnotatedClass ac, - List properties) { + List properties) { JsonAppend ann = _findAnnotation(ac, JsonAppend.class); if (ann == null) { return; @@ -911,10 +908,9 @@ public void findAndAddVirtualProperties(MapperConfig config, AnnotatedClass a } protected BeanPropertyWriter _constructVirtualProperty(JsonAppend.Attr attr, - MapperConfig config, AnnotatedClass ac, JavaType type) - { + MapperConfig config, AnnotatedClass ac, JavaType type) { PropertyMetadata metadata = attr.required() ? - PropertyMetadata.STD_REQUIRED : PropertyMetadata.STD_OPTIONAL; + PropertyMetadata.STD_REQUIRED : PropertyMetadata.STD_OPTIONAL; // could add Index, Description in future, if those matter String attrName = attr.value(); @@ -935,10 +931,9 @@ protected BeanPropertyWriter _constructVirtualProperty(JsonAppend.Attr attr, } protected BeanPropertyWriter _constructVirtualProperty(JsonAppend.Prop prop, - MapperConfig config, AnnotatedClass ac) - { + MapperConfig config, AnnotatedClass ac) { PropertyMetadata metadata = prop.required() ? - PropertyMetadata.STD_REQUIRED : PropertyMetadata.STD_OPTIONAL; + PropertyMetadata.STD_REQUIRED : PropertyMetadata.STD_OPTIONAL; PropertyName propName = _propertyName(prop.name(), prop.namespace()); JavaType type = config.constructType(prop.type()); // now, then, we need a placeholder for member (no real Field/Method): @@ -969,8 +964,7 @@ protected BeanPropertyWriter _constructVirtualProperty(JsonAppend.Prop prop, */ @Override - public PropertyName findNameForSerialization(Annotated a) - { + public PropertyName findNameForSerialization(Annotated a) { boolean useDefault = false; JsonGetter jg = _findAnnotation(a, JsonGetter.class); if (jg != null) { @@ -1016,8 +1010,7 @@ public Boolean hasAnyGetter(Annotated a) { */ @Override - public Object findDeserializer(MapperConfig config, Annotated a) - { + public Object findDeserializer(MapperConfig config, Annotated a) { JsonDeserialize ann = _findAnnotation(a, JsonDeserialize.class); if (ann != null) { @SuppressWarnings("rawtypes") @@ -1030,8 +1023,7 @@ public Object findDeserializer(MapperConfig config, Annotated a) } @Override - public Object findKeyDeserializer(MapperConfig config, Annotated a) - { + public Object findKeyDeserializer(MapperConfig config, Annotated a) { JsonDeserialize ann = _findAnnotation(a, JsonDeserialize.class); if (ann != null) { Class deserClass = ann.keyUsing(); @@ -1043,8 +1035,7 @@ public Object findKeyDeserializer(MapperConfig config, Annotated a) } @Override - public Object findContentDeserializer(MapperConfig config, Annotated a) - { + public Object findContentDeserializer(MapperConfig config, Annotated a) { JsonDeserialize ann = _findAnnotation(a, JsonDeserialize.class); if (ann != null) { @SuppressWarnings("rawtypes") @@ -1057,15 +1048,13 @@ public Object findContentDeserializer(MapperConfig config, Annotated a) } @Override - public Object findDeserializationConverter(MapperConfig config, Annotated a) - { + public Object findDeserializationConverter(MapperConfig config, Annotated a) { JsonDeserialize ann = _findAnnotation(a, JsonDeserialize.class); return (ann == null) ? null : _classIfExplicit(ann.converter(), Converter.None.class); } @Override - public Object findDeserializationContentConverter(MapperConfig config, AnnotatedMember a) - { + public Object findDeserializationContentConverter(MapperConfig config, AnnotatedMember a) { JsonDeserialize ann = _findAnnotation(a, JsonDeserialize.class); return (ann == null) ? null : _classIfExplicit(ann.contentConverter(), Converter.None.class); } @@ -1078,13 +1067,12 @@ public Object findDeserializationContentConverter(MapperConfig config, Annota @Override public JavaType refineDeserializationType(final MapperConfig config, - final Annotated a, final JavaType baseType) throws JsonMappingException - { + final Annotated a, final JavaType baseType) throws JsonMappingException { JavaType type = baseType; final TypeFactory tf = config.getTypeFactory(); final JsonDeserialize jsonDeser = _findAnnotation(a, JsonDeserialize.class); - + // Ok: start by refining the main type itself; common to all types final Class valueClass = (jsonDeser == null) ? null : _classIfExplicit(jsonDeser.as()); if ((valueClass != null) && !type.hasRawClass(valueClass) @@ -1095,7 +1083,7 @@ public JavaType refineDeserializationType(final MapperConfig config, throw new JsonMappingException(null, String.format("Failed to narrow type %s with annotation (value %s), from '%s': %s", type, valueClass.getName(), a.getName(), iae.getMessage()), - iae); + iae); } } // Then further processing for container types @@ -1113,7 +1101,7 @@ public JavaType refineDeserializationType(final MapperConfig config, throw new JsonMappingException(null, String.format("Failed to narrow key type of %s with concrete-type annotation (value %s), from '%s': %s", type, keyClass.getName(), a.getName(), iae.getMessage()), - iae); + iae); } } } @@ -1144,23 +1132,20 @@ public JavaType refineDeserializationType(final MapperConfig config, */ @Override - public Object findValueInstantiator(MapperConfig config, AnnotatedClass ac) - { + public Object findValueInstantiator(MapperConfig config, AnnotatedClass ac) { JsonValueInstantiator ann = _findAnnotation(ac, JsonValueInstantiator.class); // no 'null' marker yet, so: return (ann == null) ? null : ann.value(); } @Override - public Class findPOJOBuilder(MapperConfig config, AnnotatedClass ac) - { + public Class findPOJOBuilder(MapperConfig config, AnnotatedClass ac) { JsonDeserialize ann = _findAnnotation(ac, JsonDeserialize.class); return (ann == null) ? null : _classIfExplicit(ann.builder()); } @Override - public JsonPOJOBuilder.Value findPOJOBuilderConfig(MapperConfig config, AnnotatedClass ac) - { + public JsonPOJOBuilder.Value findPOJOBuilderConfig(MapperConfig config, AnnotatedClass ac) { JsonPOJOBuilder ann = _findAnnotation(ac, JsonPOJOBuilder.class); return (ann == null) ? null : new JsonPOJOBuilder.Value(ann); } @@ -1172,8 +1157,7 @@ public JsonPOJOBuilder.Value findPOJOBuilderConfig(MapperConfig config, Annot */ @Override - public PropertyName findNameForDeserialization(Annotated a) - { + public PropertyName findNameForDeserialization(Annotated a) { // @JsonSetter has precedence over @JsonProperty, being more specific boolean useDefault = false; @@ -1222,7 +1206,7 @@ public JsonCreator.Mode findCreatorAnnotation(MapperConfig config, Annotated } if (_cfgConstructorPropertiesImpliesCreator && config.isEnabled(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES) - ) { + ) { if (_javaBeansHelper != null) { Boolean b = _javaBeansHelper.hasCreatorAnnotation(a); if ((b != null) && b.booleanValue()) { @@ -1241,8 +1225,7 @@ public JsonCreator.Mode findCreatorAnnotation(MapperConfig config, Annotated /********************************************************** */ - protected boolean _isIgnorable(Annotated a) - { + protected boolean _isIgnorable(Annotated a) { JsonIgnore ann = _findAnnotation(a, JsonIgnore.class); if (ann != null) { return ann.value(); @@ -1279,8 +1262,7 @@ protected PropertyName _propertyName(String localName, String namespace) { return PropertyName.construct(localName, namespace); } - private boolean _primitiveAndWrapper(Class baseType, Class refinement) - { + private boolean _primitiveAndWrapper(Class baseType, Class refinement) { if (baseType.isPrimitive()) { return baseType == ClassUtil.primitiveType(refinement); } @@ -1290,8 +1272,7 @@ private boolean _primitiveAndWrapper(Class baseType, Class refinement) return false; } - private boolean _primitiveAndWrapper(JavaType baseType, Class refinement) - { + private boolean _primitiveAndWrapper(JavaType baseType, Class refinement) { if (baseType.isPrimitive()) { return baseType.hasRawClass(ClassUtil.primitiveType(refinement)); } diff --git a/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java b/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java index 0f05917533..5973444b03 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java @@ -1,10 +1,10 @@ package com.fasterxml.jackson.databind.util; -import java.util.*; - import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; +import java.util.*; + /** * Helper class used to resolve String values (either JSON Object field * names or regular String values) into Java Enum instances. @@ -40,6 +40,7 @@ public static EnumResolver constructFor(Class> enumCls, AnnotationIntros throw new IllegalArgumentException("No enum constants for class "+enumCls.getName()); } String[] names = ai.findEnumValues(enumCls, enumValues, new String[enumValues.length]); + Map aliasMap = ai.findEnumAliases(enumCls); HashMap> map = new HashMap>(); for (int i = 0, len = enumValues.length; i < len; ++i) { String name = names[i]; @@ -47,6 +48,7 @@ public static EnumResolver constructFor(Class> enumCls, AnnotationIntros name = enumValues[i].name(); } map.put(name, enumValues[i]); + addAliasesToEnumValueMap(enumValues[i], aliasMap, map, name); } Enum defaultEnum = ai.findDefaultEnumValue(enumCls); @@ -54,6 +56,18 @@ public static EnumResolver constructFor(Class> enumCls, AnnotationIntros return new EnumResolver(enumCls, enumValues, map, defaultEnum); } + // for [databind#2352]: Support aliases on enum values + private static void addAliasesToEnumValueMap(Enum enumValue, Map aliasMap, HashMap> map, String name) { + if(!aliasMap.isEmpty()){ + String[] aliases = aliasMap.get(name); + if(aliases != null){ + for(String alias : aliases){ + map.put(alias, enumValue); + } + } + } + } + /** * Factory method for constructing resolver that maps from Enum.toString() into * Enum value @@ -61,15 +75,17 @@ public static EnumResolver constructFor(Class> enumCls, AnnotationIntros public static EnumResolver constructUsingToString(Class> enumCls, AnnotationIntrospector ai) { - Enum[] enumValues = enumCls.getEnumConstants(); + Enum[] enumConstants = enumCls.getEnumConstants(); HashMap> map = new HashMap>(); + Map aliasMap = ai.findEnumAliases(enumCls); // from last to first, so that in case of duplicate values, first wins - for (int i = enumValues.length; --i >= 0; ) { - Enum e = enumValues[i]; - map.put(e.toString(), e); + for (int i = enumConstants.length; --i >= 0; ) { + Enum enumConstant = enumConstants[i]; + map.put(enumConstant.toString(), enumConstant); + addAliasesToEnumValueMap(enumConstants[i], aliasMap, map, enumConstant.name()); } Enum defaultEnum = (ai == null) ? null : ai.findDefaultEnumValue(enumCls); - return new EnumResolver(enumCls, enumValues, map, defaultEnum); + return new EnumResolver(enumCls, enumConstants, map, defaultEnum); } public static EnumResolver constructUsingMethod(Class> enumCls, diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDeserializationTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDeserializationTest.java index a62750c024..1c3848c8bc 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDeserializationTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/EnumDeserializationTest.java @@ -1,13 +1,8 @@ package com.fasterxml.jackson.databind.deser.jdk; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.TimeUnit; - import com.fasterxml.jackson.annotation.*; -import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; - import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer; @@ -17,6 +12,12 @@ import com.fasterxml.jackson.databind.exc.ValueInstantiationException; import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Map; +import java.util.concurrent.TimeUnit; + @SuppressWarnings("serial") public class EnumDeserializationTest extends BaseMapTest @@ -132,7 +133,7 @@ static enum StrictEnumCreator { } // // - + public enum AnEnum { ZERO, ONE @@ -206,7 +207,38 @@ private Enum2309(String value) { public String toString() { return value; } - } + } + + // for [databind#2352]: Support aliases on enum values + enum MyEnum2352_1 { + A, + @JsonAlias({"singleAlias"}) + B, + @JsonAlias({"multipleAliases1", "multipleAliases2"}) + C + } + + enum MyEnum2352_2 { + A, + @JsonAlias({"singleAlias"}) + B, + @JsonAlias({"multipleAliases1", "multipleAliases2"}) + C; + + @Override + public String toString() { + return name().toLowerCase(); + } + } + + enum MyEnum2352_3 { + A, + @JsonEnumDefaultValue + @JsonAlias({"singleAlias"}) + B, + @JsonAlias({"multipleAliases1", "multipleAliases2"}) + C + } /* /********************************************************** @@ -256,7 +288,7 @@ public void testComplexEnum() throws Exception TimeUnit result = MAPPER.readValue(json, TimeUnit.class); assertSame(TimeUnit.SECONDS, result); } - + /** * Testing to see that annotation override works */ @@ -425,7 +457,7 @@ public void testUnwrappedEnum() throws Exception { .with(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS) .readValue("[" + quote("JACKSON") + "]")); } - + public void testUnwrappedEnumException() throws Exception { final ObjectMapper mapper = jsonMapperBuilder() .disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS) @@ -499,7 +531,7 @@ public void testDeserWithToString1161() throws Exception .readValue(quote("A")); assertSame(Enum1161.A, result); } - + public void testEnumWithDefaultAnnotation() throws Exception { final ObjectMapper mapper = jsonMapperBuilder() .enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE) @@ -601,4 +633,49 @@ public void testEnumToStringNull2309() throws Exception assertEquals(Enum2309.NON_NULL, value); } + // for [databind#2352] + public void testEnumWithAlias() throws Exception { + ObjectReader reader = MAPPER.readerFor(MyEnum2352_1.class); + MyEnum2352_1 nonAliased = reader.readValue(quote("A")); + assertEquals(MyEnum2352_1.A, nonAliased); + MyEnum2352_1 singleAlias = reader.readValue(quote("singleAlias")); + assertEquals(MyEnum2352_1.B, singleAlias); + MyEnum2352_1 multipleAliases1 = reader.readValue(quote("multipleAliases1")); + assertEquals(MyEnum2352_1.C, multipleAliases1); + MyEnum2352_1 multipleAliases2 = reader.readValue(quote("multipleAliases2")); + assertEquals(MyEnum2352_1.C, multipleAliases2); + } + + // for [databind#2352] + public void testEnumWithAliasAndToStringSupported() throws Exception { + ObjectReader reader = MAPPER.readerFor(MyEnum2352_2.class) + .with(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + + MyEnum2352_2 nonAliased = reader.readValue(quote("a")); + assertEquals(MyEnum2352_2.A, nonAliased); + MyEnum2352_2 singleAlias = reader.readValue(quote("singleAlias")); + assertEquals(MyEnum2352_2.B, singleAlias); + MyEnum2352_2 multipleAliases1 = reader.readValue(quote("multipleAliases1")); + assertEquals(MyEnum2352_2.C, multipleAliases1); + MyEnum2352_2 multipleAliases2 = reader.readValue(quote("multipleAliases2")); + assertEquals(MyEnum2352_2.C, multipleAliases2); + } + + // for [databind#2352] + public void testEnumWithAliasAndDefaultForUnknownValueEnabled() throws Exception { + ObjectReader reader = MAPPER.readerFor(MyEnum2352_3.class) + .with(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE); + + MyEnum2352_3 nonAliased = reader.readValue(quote("A")); + assertEquals(MyEnum2352_3.A, nonAliased); + MyEnum2352_3 singleAlias = reader.readValue(quote("singleAlias")); + assertEquals(MyEnum2352_3.B, singleAlias); + MyEnum2352_3 defaulted = reader.readValue(quote("unknownValue")); + assertEquals(MyEnum2352_3.B, defaulted); + MyEnum2352_3 multipleAliases1 = reader.readValue(quote("multipleAliases1")); + assertEquals(MyEnum2352_3.C, multipleAliases1); + MyEnum2352_3 multipleAliases2 = reader.readValue(quote("multipleAliases2")); + assertEquals(MyEnum2352_3.C, multipleAliases2); + + } } diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestEnumSerialization.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestEnumSerialization.java index 1e18f360af..90e21a9345 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/TestEnumSerialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestEnumSerialization.java @@ -1,11 +1,9 @@ package com.fasterxml.jackson.databind.ser; -import java.io.*; -import java.util.*; - -import com.fasterxml.jackson.annotation.*; - -import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; @@ -13,6 +11,13 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import java.io.IOException; +import java.io.StringWriter; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + /** * Unit tests for verifying serialization of simple basic non-structured * types; primitives (and/or their wrappers), Strings. @@ -242,7 +247,7 @@ public void testAnnotationsOnEnumCtor() throws Exception { assertEquals(quote("V1"), MAPPER.writeValueAsString(OK.V1)); assertEquals(quote("V1"), MAPPER.writeValueAsString(NOT_OK.V1)); - assertEquals(quote("V2"), MAPPER.writeValueAsString(NOT_OK2.V2)); + assertEquals(quote("B"), MAPPER.writeValueAsString(NOT_OK2.V2)); } // [databind#227]