Skip to content

Commit

Permalink
Fix #2873: Added case sensitivity check flag from MapperConfig to Enu…
Browse files Browse the repository at this point in the history
…mResolver
  • Loading branch information
ILGO0413 authored and cowtowncoder committed Oct 11, 2020
1 parent 960f4f6 commit 85edd63
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 75 deletions.
4 changes: 4 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -1221,3 +1221,7 @@ Jendrik Johannes (jjohannes@github)
Swayam Raina (swayamraina@github)
* Contributed #2761: Support multiple names in `JsonSubType.Type`
(2.12.0)
Ilya Golovin (ilgo0413@github)
* Contributed #2873: `MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS` should work for enum as keys
(2.12.0)
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Project: jackson-databind
#2800: Extract getter/setter/field name mangling from `BeanUtil` into
pluggable `AccessorNamingStrategy`
#2805: Remove `JsonProcessingException` from `ObjectMapper.treeToValue()`
#2873: `MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS` should work for enum as keys
(fix contributed by Ilya G)
- Add `BeanDeserializerBase.isCaseInsensitive()`
- Some refactoring of `CollectionDeserializer` to solve CSV array handling issues

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,10 @@ public enum MapperFeature implements ConfigFeature
* If enabled, Enum deserialization will ignore case, that is, case of incoming String
* value and enum id (depending on other settings, either `name()`, `toString()`, or
* explicit override) do not need to match.
* <p>
*<p>
* This should allow both Enum-as-value deserialization and Enum-as-Map-key, but latter
* only works since Jackson 2.12 (due to incomplete implementation).
*<p>
* Feature is disabled by default.
*
* @since 2.9
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2370,12 +2370,11 @@ protected EnumResolver constructEnumResolver(Class<?> enumClass,
ClassUtil.checkAndFixAccess(jsonValueAccessor.getMember(),
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
return EnumResolver.constructUnsafeUsingMethod(enumClass,
jsonValueAccessor, config.getAnnotationIntrospector());
return EnumResolver.constructUsingMethod(config, enumClass, jsonValueAccessor);
}
// 14-Mar-2016, tatu: We used to check `DeserializationFeature.READ_ENUMS_USING_TO_STRING`
// here, but that won't do: it must be dynamically changeable...
return EnumResolver.constructUnsafe(enumClass, config.getAnnotationIntrospector());
return EnumResolver.constructFor(config, enumClass);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,7 @@ protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt)
// reduce contention for the initial resolution
if (lookup == null) {
synchronized (this) {
lookup = EnumResolver.constructUnsafeUsingToString(_enumClass(),
ctxt.getAnnotationIntrospector())
lookup = EnumResolver.constructUsingToString(ctxt.getConfig(), _enumClass())
.constructLookup();
}
_lookupByToString = lookup;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,8 @@ private EnumResolver _getToStringResolver(DeserializationContext ctxt)
EnumResolver res = _byToStringResolver;
if (res == null) {
synchronized (this) {
res = EnumResolver.constructUnsafeUsingToString(_byNameResolver.getEnumClass(),
ctxt.getAnnotationIntrospector());
res = EnumResolver.constructUsingToString(ctxt.getConfig(),
_byNameResolver.getEnumClass());
_byToStringResolver = res;
}
}
Expand Down
241 changes: 176 additions & 65 deletions src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.util.*;

import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;

/**
Expand All @@ -21,31 +23,51 @@ public class EnumResolver implements java.io.Serializable

protected final Enum<?> _defaultValue;

/**
* @since 2.12
*/
protected final boolean _isIgnoreCase;

/**
* @since 2.12
*/
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
HashMap<String, Enum<?>> map, Enum<?> defaultValue)
HashMap<String, Enum<?>> map, Enum<?> defaultValue,
boolean isIgnoreCase)
{
_enumClass = enumClass;
_enums = enums;
_enumsById = map;
_defaultValue = defaultValue;
_isIgnoreCase = isIgnoreCase;
}

/**
* Factory method for constructing resolver that maps from Enum.name() into
* Enum value
* Enum value.
*
* @since 2.12
*/
public static EnumResolver constructFor(DeserializationConfig config,
Class<?> enumCls) {
return _constructFor(enumCls, config.getAnnotationIntrospector(),
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));
}

/**
* @since 2.12
*/
public static EnumResolver constructFor(Class<Enum<?>> enumCls, AnnotationIntrospector ai)
protected static EnumResolver _constructFor(Class<?> enumCls0,
AnnotationIntrospector ai, boolean isIgnoreCase)
{
Enum<?>[] enumValues = enumCls.getEnumConstants();
if (enumValues == null) {
throw new IllegalArgumentException("No enum constants for class "+enumCls.getName());
}
String[] names = ai.findEnumValues(enumCls, enumValues, new String[enumValues.length]);
final Class<Enum<?>> enumCls = _enumClass(enumCls0);
final Enum<?>[] enumConstants = _enumConstants(enumCls0);
String[] names = ai.findEnumValues(enumCls, enumConstants, new String[enumConstants.length]);
final String[][] allAliases = new String[names.length][];
ai.findEnumAliases(enumCls, enumValues, allAliases);
ai.findEnumAliases(enumCls, enumConstants, allAliases);
HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
for (int i = 0, len = enumValues.length; i < len; ++i) {
final Enum<?> enumValue = enumValues[i];
for (int i = 0, len = enumConstants.length; i < len; ++i) {
final Enum<?> enumValue = enumConstants[i];
String name = names[i];
if (name == null) {
name = enumValue.name();
Expand All @@ -62,27 +84,30 @@ public static EnumResolver constructFor(Class<Enum<?>> enumCls, AnnotationIntros
}
}
}
return new EnumResolver(enumCls, enumValues, map, ai.findDefaultEnumValue(enumCls));
return new EnumResolver(enumCls, enumConstants, map,
_enumDefault(ai, enumCls), isIgnoreCase);
}

/**
* @deprecated Since 2.8, use {@link #constructUsingToString(Class, AnnotationIntrospector)} instead
* Factory method for constructing resolver that maps from Enum.toString() into
* Enum value
*
* @since 2.12
*/
@Deprecated
public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls) {
return constructUsingToString(enumCls, null);
public static EnumResolver constructUsingToString(DeserializationConfig config,
Class<?> enumCls) {
return _constructUsingToString(enumCls, config.getAnnotationIntrospector(),
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));
}

/**
* Factory method for constructing resolver that maps from Enum.toString() into
* Enum value
*
* @since 2.8
* @since 2.12
*/
public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls,
AnnotationIntrospector ai)
protected static EnumResolver _constructUsingToString(Class<?> enumCls0,
AnnotationIntrospector ai, boolean isIgnoreCase)
{
Enum<?>[] enumConstants = enumCls.getEnumConstants();
final Class<Enum<?>> enumCls = _enumClass(enumCls0);
final Enum<?>[] enumConstants = _enumConstants(enumCls0);
HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
final String[][] allAliases = new String[enumConstants.length][];
ai.findEnumAliases(enumCls, enumConstants, allAliases);
Expand All @@ -102,21 +127,34 @@ public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls,
}
}
}
return new EnumResolver(enumCls, enumConstants, map, ai.findDefaultEnumValue(enumCls));
return new EnumResolver(enumCls, enumConstants, map,
_enumDefault(ai, enumCls), isIgnoreCase);
}

/**
* @since 2.9
* Method used when actual String serialization is indicated using @JsonValue
* on a method in Enum class.
*
* @since 2.12
*/
public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls,
AnnotatedMember accessor,
AnnotationIntrospector ai)
public static EnumResolver constructUsingMethod(DeserializationConfig config,
Class<?> enumCls, AnnotatedMember accessor) {
return _constructUsingMethod(enumCls, accessor, config.getAnnotationIntrospector(),
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));
}

/**
* @since 2.12
*/
protected static EnumResolver _constructUsingMethod(Class<?> enumCls0,
AnnotatedMember accessor, AnnotationIntrospector ai, boolean isIgnoreCase)
{
Enum<?>[] enumValues = enumCls.getEnumConstants();
final Class<Enum<?>> enumCls = _enumClass(enumCls0);
final Enum<?>[] enumConstants = _enumConstants(enumCls0);
HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
// from last to first, so that in case of duplicate values, first wins
for (int i = enumValues.length; --i >= 0; ) {
Enum<?> en = enumValues[i];
for (int i = enumConstants.length; --i >= 0; ) {
Enum<?> en = enumConstants[i];
try {
Object o = accessor.getValue(en);
if (o != null) {
Expand All @@ -126,60 +164,133 @@ public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls,
throw new IllegalArgumentException("Failed to access @JsonValue of Enum value "+en+": "+e.getMessage());
}
}
Enum<?> defaultEnum = (ai != null) ? ai.findDefaultEnumValue(enumCls) : null;
return new EnumResolver(enumCls, enumValues, map, defaultEnum);
return new EnumResolver(enumCls, enumConstants, map,
_enumDefault(ai, enumCls), isIgnoreCase);
}

public CompactStringObjectMap constructLookup() {
return CompactStringObjectMap.construct(_enumsById);
}

@SuppressWarnings("unchecked")
protected static Class<Enum<?>> _enumClass(Class<?> enumCls0) {
return (Class<Enum<?>>) enumCls0;
}

protected static Enum<?>[] _enumConstants(Class<?> enumCls) {
final Enum<?>[] enumValues = _enumClass(enumCls).getEnumConstants();
if (enumValues == null) {
throw new IllegalArgumentException("No enum constants for class "+enumCls.getName());
}
return enumValues;
}

protected static Enum<?> _enumDefault(AnnotationIntrospector intr, Class<?> enumCls) {
return (intr != null) ? intr.findDefaultEnumValue(_enumClass(enumCls)) : null;
}

/*
/**********************************************************************
/* Deprecated constructors, factory methods
/**********************************************************************
*/

/**
* This method is needed because of the dynamic nature of constructing Enum
* resolvers.
* @deprecated Since 2.12 (remove from 2.13+ not part of public API)
*/
@SuppressWarnings({ "unchecked" })
public static EnumResolver constructUnsafe(Class<?> rawEnumCls, AnnotationIntrospector ai)
{
/* This is oh so wrong... but at least ugliness is mostly hidden in just
* this one place.
*/
Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls;
return constructFor(enumCls, ai);
@Deprecated // since 2.12
protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
HashMap<String, Enum<?>> map, Enum<?> defaultValue) {
this(enumClass, enums, map, defaultValue, false);
}

/**
* @deprecated Since 2.12
*/
@Deprecated // since 2.12
public static EnumResolver constructFor(Class<Enum<?>> enumCls, AnnotationIntrospector ai) {
return _constructFor(enumCls, ai, false);
}

/**
* @deprecated Since 2.12
*/
@Deprecated // since 2.12
public static EnumResolver constructUnsafe(Class<?> rawEnumCls, AnnotationIntrospector ai) {
return _constructFor(rawEnumCls, ai, false);
}

/**
* @deprecated Since 2.12
*/
@Deprecated // since 2.12
public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls,
AnnotationIntrospector ai) {
return _constructUsingToString(enumCls, ai, false);
}

/**
* Method that needs to be used instead of {@link #constructUsingToString}
* if static type of enum is not known.
*
* @since 2.8
* @deprecated Since 2.12
*/
@SuppressWarnings({ "unchecked" })
@Deprecated // since 2.12
public static EnumResolver constructUnsafeUsingToString(Class<?> rawEnumCls,
AnnotationIntrospector ai)
{
// oh so wrong... not much that can be done tho
Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls;
return constructUsingToString(enumCls, ai);
AnnotationIntrospector ai) {
return _constructUsingToString(rawEnumCls, ai, false);
}

/**
* @deprecated Since 2.8 (remove from 2.13 or later)
*/
@Deprecated
public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls) {
return _constructUsingToString(enumCls, null, false);
}

/**
* @deprecated Since 2.12
*/
@Deprecated
public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls,
AnnotatedMember accessor, AnnotationIntrospector ai) {
return _constructUsingMethod(enumCls, accessor, ai, false);
}

/**
* Method used when actual String serialization is indicated using @JsonValue
* on a method.
*
* @since 2.9
* @deprecated Since 2.12
*/
@SuppressWarnings({ "unchecked" })
@Deprecated
public static EnumResolver constructUnsafeUsingMethod(Class<?> rawEnumCls,
AnnotatedMember accessor,
AnnotationIntrospector ai)
{
// wrong as ever but:
Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls;
return constructUsingMethod(enumCls, accessor, ai);
AnnotatedMember accessor, AnnotationIntrospector ai) {
return _constructUsingMethod(rawEnumCls, accessor, ai, false);
}

public CompactStringObjectMap constructLookup() {
return CompactStringObjectMap.construct(_enumsById);
/*
/**********************************************************************
/* Public API
/**********************************************************************
*/

public Enum<?> findEnum(final String key) {
Enum<?> en = _enumsById.get(key);
if (en == null) {
if (_isIgnoreCase) {
return _findEnumCaseInsensitive(key);
}
}
return en;
}

public Enum<?> findEnum(String key) { return _enumsById.get(key); }
// @since 2.12
protected Enum<?> _findEnumCaseInsensitive(final String key) {
for (Map.Entry<String, Enum<?>> entry : _enumsById.entrySet()) {
if (key.equalsIgnoreCase(entry.getKey())) {
return entry.getValue();
}
}
return null;
}

public Enum<?> getEnum(int index) {
if (index < 0 || index >= _enums.length) {
Expand Down
Loading

0 comments on commit 85edd63

Please sign in to comment.