All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fasterxml.jackson.databind.util.EnumResolver Maven / Gradle / Ivy

There is a newer version: 2.18.1
Show newest version
package com.fasterxml.jackson.databind.util;

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.EnumNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;

/**
 * Helper class used to resolve String values (either JSON Object field
 * names or regular String values) into Java Enum instances.
 */
public class EnumResolver implements java.io.Serializable
{
    private static final long serialVersionUID = 1L;

    protected final Class> _enumClass;

    protected final Enum[] _enums;

    protected final HashMap> _enumsById;

    protected final Enum _defaultValue;

    /**
     * Marker for case-insensitive handling
     *
     * @since 2.12
     */
    protected final boolean _isIgnoreCase;

    /**
     * Marker for case where value may come from {@code @JsonValue} annotated
     * accessor and is expected/likely to come from actual integral number
     * value (and not String).
     *

* Special case is needed since this specifically means that {@code Enum.index()} * should NOT be used or default to. * * @since 2.13 */ protected final boolean _isFromIntValue; /* /********************************************************************** /* Constructors /********************************************************************** */ /** * @since 2.12 */ protected EnumResolver(Class> enumClass, Enum[] enums, HashMap> map, Enum defaultValue, boolean isIgnoreCase, boolean isFromIntValue) { _enumClass = enumClass; _enums = enums; _enumsById = map; _defaultValue = defaultValue; _isIgnoreCase = isIgnoreCase; _isFromIntValue = isFromIntValue; } /* /********************************************************************** /* Factory methods /********************************************************************** */ /** * Factory method for constructing an {@link EnumResolver} based on the given {@link DeserializationConfig} and * {@link AnnotatedClass} of the enum to be resolved. * * @param config the deserialization configuration to use * @param annotatedClass the annotated class of the enum to be resolved * @return the constructed {@link EnumResolver} * * @since 2.16 */ public static EnumResolver constructFor(DeserializationConfig config, AnnotatedClass annotatedClass) { // prepare data final AnnotationIntrospector ai = config.getAnnotationIntrospector(); final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); final Class enumCls0 = annotatedClass.getRawType(); final Class> enumCls = _enumClass(enumCls0); final Enum[] enumConstants = _enumConstants(enumCls0); // introspect String[] names = ai.findEnumValues(config, annotatedClass, enumConstants, new String[enumConstants.length]); final String[][] allAliases = new String[names.length][]; ai.findEnumAliases(config, annotatedClass, enumConstants, allAliases); // finally, build HashMap> map = new HashMap>(); 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(); } map.put(name, enumValue); String[] aliases = allAliases[i]; if (aliases != null) { for (String alias : aliases) { // Avoid overriding any primary names map.putIfAbsent(alias, enumValue); } } } return new EnumResolver(enumCls, enumConstants, map, _enumDefault(ai, annotatedClass, enumConstants), isIgnoreCase, false); } /** * Factory method for constructing resolver that maps from Enum.name() into * Enum value. * * @since 2.12 * @deprecated Since 2.16 use {@link #constructFor(DeserializationConfig, AnnotatedClass)} instead */ @Deprecated public static EnumResolver constructFor(DeserializationConfig config, Class enumCls0) { final AnnotationIntrospector ai = config.getAnnotationIntrospector(); final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); final Class> 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, enumConstants, allAliases); HashMap> map = new HashMap>(); 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(); } map.put(name, enumValue); String[] aliases = allAliases[i]; if (aliases != null) { for (String alias : aliases) { // Avoid overriding any primary names map.putIfAbsent(alias, enumValue); } } } return new EnumResolver(enumCls, enumConstants, map, _enumDefault(ai, enumCls), isIgnoreCase, false); } /** * Factory method for constructing resolver that maps from Enum.toString() into * Enum value * * @since 2.16 */ public static EnumResolver constructUsingToString(DeserializationConfig config, AnnotatedClass annotatedClass) { // prepare data final AnnotationIntrospector ai = config.getAnnotationIntrospector(); final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); final Class enumCls0 = annotatedClass.getRawType(); final Class> enumCls = _enumClass(enumCls0); final Enum[] enumConstants = _enumConstants(enumCls0); // introspect final String[] names = new String[enumConstants.length]; final String[][] allAliases = new String[enumConstants.length][]; if (ai != null) { ai.findEnumValues(config, annotatedClass, enumConstants, names); ai.findEnumAliases(config, annotatedClass, enumConstants, allAliases); } // finally, build // from last to first, so that in case of duplicate values, first wins HashMap> map = new HashMap>(); for (int i = enumConstants.length; --i >= 0; ) { Enum enumValue = enumConstants[i]; String name = names[i]; if (name == null) { name = enumValue.toString(); } map.put(name, enumValue); String[] aliases = allAliases[i]; if (aliases != null) { for (String alias : aliases) { // Avoid overriding any primary names map.putIfAbsent(alias, enumValue); } } } return new EnumResolver(enumCls, enumConstants, map, _enumDefault(ai, annotatedClass, enumConstants), isIgnoreCase, false); } /** * Factory method for constructing resolver that maps from Enum.toString() into * Enum value * * @since 2.12 * @deprecated Since 2.16 use {@link #constructUsingToString(DeserializationConfig, AnnotatedClass)} instead */ @Deprecated public static EnumResolver constructUsingToString(DeserializationConfig config, Class enumCls0) { final AnnotationIntrospector ai = config.getAnnotationIntrospector(); final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); final Class> enumCls = _enumClass(enumCls0); final Enum[] enumConstants = _enumConstants(enumCls0); HashMap> map = new HashMap>(); final String[][] allAliases = new String[enumConstants.length][]; if (ai != null) { ai.findEnumAliases(enumCls, enumConstants, allAliases); } // from last to first, so that in case of duplicate values, first wins for (int i = enumConstants.length; --i >= 0; ) { Enum enumValue = enumConstants[i]; map.put(enumValue.toString(), enumValue); String[] aliases = allAliases[i]; if (aliases != null) { for (String alias : aliases) { // Avoid overriding any primary names map.putIfAbsent(alias, enumValue); } } } return new EnumResolver(enumCls, enumConstants, map, _enumDefault(ai, enumCls), isIgnoreCase, false); } /** * Factory method for constructing resolver that maps from index of Enum.values() into * Enum value. * * @since 2.16 */ public static EnumResolver constructUsingIndex(DeserializationConfig config, AnnotatedClass annotatedClass) { // prepare data final AnnotationIntrospector ai = config.getAnnotationIntrospector(); final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); final Class enumCls0 = annotatedClass.getRawType(); final Class> enumCls = _enumClass(enumCls0); final Enum[] enumConstants = _enumConstants(enumCls0); // finally, build // from last to first, so that in case of duplicate values, first wins HashMap> map = new HashMap<>(); for (int i = enumConstants.length; --i >= 0; ) { Enum enumValue = enumConstants[i]; map.put(String.valueOf(i), enumValue); } return new EnumResolver(enumCls, enumConstants, map, _enumDefault(ai, annotatedClass, enumConstants), isIgnoreCase, false); } /** * Factory method for constructing resolver that maps from index of Enum.values() into * Enum value * * @since 2.15 * @deprecated Since 2.16. Use {@link #constructUsingIndex(DeserializationConfig, AnnotatedClass)} instead. */ @Deprecated public static EnumResolver constructUsingIndex(DeserializationConfig config, Class> enumCls0) { final AnnotationIntrospector ai = config.getAnnotationIntrospector(); final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); final Class> enumCls = _enumClass(enumCls0); final Enum[] enumConstants = _enumConstants(enumCls0); HashMap> map = new HashMap<>(); // from last to first, so that in case of duplicate values, first wins for (int i = enumConstants.length; --i >= 0; ) { Enum enumValue = enumConstants[i]; map.put(String.valueOf(i), enumValue); } return new EnumResolver(enumCls, enumConstants, map, _enumDefault(ai, enumCls), isIgnoreCase, false); } /** * Factory method for constructing resolver that maps the name of enums converted to external property * names into Enum value using an implementation of {@link EnumNamingStrategy}. * * The output {@link EnumResolver} should contain values that are symmetric to * {@link EnumValues#constructUsingEnumNamingStrategy(MapperConfig, Class, EnumNamingStrategy)}. * @since 2.15 * @deprecated Since 2.16. Use * {@link #constructUsingEnumNamingStrategy(DeserializationConfig, AnnotatedClass, EnumNamingStrategy)}. */ @Deprecated public static EnumResolver constructUsingEnumNamingStrategy(DeserializationConfig config, Class enumCls0, EnumNamingStrategy enumNamingStrategy) { final AnnotationIntrospector ai = config.getAnnotationIntrospector(); final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); final Class> enumCls = _enumClass(enumCls0); final Enum[] enumConstants = _enumConstants(enumCls0); HashMap> map = new HashMap<>(); // introspect final String[] names = new String[enumConstants.length]; final String[][] allAliases = new String[enumConstants.length][]; if (ai != null) { ai.findEnumValues(enumCls, enumConstants, names); ai.findEnumAliases(enumCls, enumConstants, allAliases); } // from last to first, so that in case of duplicate values, first wins for (int i = enumConstants.length; --i >= 0; ) { Enum anEnum = enumConstants[i]; String name = names[i]; if (name == null) { name = enumNamingStrategy.convertEnumToExternalName(anEnum.name()); } map.put(name, anEnum); String[] aliases = allAliases[i]; if (aliases != null) { for (String alias : aliases) { // avoid replacing any primary names map.putIfAbsent(alias, anEnum); } } } return new EnumResolver(enumCls, enumConstants, map, _enumDefault(ai, enumCls), isIgnoreCase, false); } /** * Factory method for constructing an {@link EnumResolver} with {@link EnumNamingStrategy} applied. * * @since 2.16 */ public static EnumResolver constructUsingEnumNamingStrategy(DeserializationConfig config, AnnotatedClass annotatedClass, EnumNamingStrategy enumNamingStrategy) { // prepare data final AnnotationIntrospector ai = config.getAnnotationIntrospector(); final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); final Class enumCls0 = annotatedClass.getRawType(); final Class> enumCls = _enumClass(enumCls0); final Enum[] enumConstants = _enumConstants(enumCls0); // introspect final String[] names = new String[enumConstants.length]; final String[][] allAliases = new String[enumConstants.length][]; if (ai != null) { ai.findEnumValues(config, annotatedClass, enumConstants, names); ai.findEnumAliases(config, annotatedClass, enumConstants, allAliases); } // finally build // from last to first, so that in case of duplicate values, first wins HashMap> map = new HashMap<>(); for (int i = enumConstants.length; --i >= 0; ) { Enum anEnum = enumConstants[i]; String name = names[i]; if (name == null) { name = enumNamingStrategy.convertEnumToExternalName(anEnum.name()); } map.put(name, anEnum); String[] aliases = allAliases[i]; if (aliases != null) { for (String alias : aliases) { // avoid replacing any primary names map.putIfAbsent(alias, anEnum); } } } return new EnumResolver(enumCls, enumConstants, map, _enumDefault(ai, annotatedClass, enumConstants), isIgnoreCase, false); } /** * Method used when actual String serialization is indicated using @JsonValue * on a method in Enum class. * * @since 2.12 * @deprecated Since 2.16. * Use {@link #constructUsingMethod(DeserializationConfig, AnnotatedClass, AnnotatedMember)} instead. */ @Deprecated public static EnumResolver constructUsingMethod(DeserializationConfig config, Class enumCls0, AnnotatedMember accessor) { final AnnotationIntrospector ai = config.getAnnotationIntrospector(); final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); final Class> enumCls = _enumClass(enumCls0); final Enum[] enumConstants = _enumConstants(enumCls0); HashMap> map = new HashMap>(); // from last to first, so that in case of duplicate values, first wins for (int i = enumConstants.length; --i >= 0; ) { Enum en = enumConstants[i]; try { Object o = accessor.getValue(en); if (o != null) { map.put(o.toString(), en); } } catch (Exception e) { throw new IllegalArgumentException("Failed to access @JsonValue of Enum value "+en+": "+e.getMessage()); } } return new EnumResolver(enumCls, enumConstants, map, _enumDefault(ai, enumCls), isIgnoreCase, // 26-Sep-2021, tatu: [databind#1850] Need to consider "from int" case _isIntType(accessor.getRawType()) ); } /** * Method used when actual String serialization is indicated using @JsonValue * on a method in Enum class. * * @since 2.16 */ public static EnumResolver constructUsingMethod(DeserializationConfig config, AnnotatedClass annotatedClass, AnnotatedMember accessor) { // prepare data final AnnotationIntrospector ai = config.getAnnotationIntrospector(); final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); final Class enumCls0 = annotatedClass.getRawType(); final Class> enumCls = _enumClass(enumCls0); final Enum[] enumConstants = _enumConstants(enumCls0); // build HashMap> map = new HashMap>(); // from last to first, so that in case of duplicate values, first wins for (int i = enumConstants.length; --i >= 0; ) { Enum en = enumConstants[i]; try { Object o = accessor.getValue(en); if (o != null) { map.put(o.toString(), en); } } catch (Exception e) { throw new IllegalArgumentException("Failed to access @JsonValue of Enum value "+en+": "+e.getMessage()); } } return new EnumResolver(enumCls, enumConstants, map, _enumDefault(ai, annotatedClass, enumConstants), isIgnoreCase, // 26-Sep-2021, tatu: [databind#1850] Need to consider "from int" case _isIntType(accessor.getRawType()) ); } public CompactStringObjectMap constructLookup() { return CompactStringObjectMap.construct(_enumsById); } @SuppressWarnings("unchecked") protected static Class> _enumClass(Class enumCls0) { return (Class>) 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; } /** * Internal helper method used to resolve Enum default value to use (if any). * * @since 2.16 * @see AnnotationIntrospector#findDefaultEnumValue(AnnotatedClass, Enum[]) */ protected static Enum _enumDefault(AnnotationIntrospector intr, AnnotatedClass annotatedClass, Enum[] enums) { return (intr != null) ? intr.findDefaultEnumValue(annotatedClass, enums) : null; } /** * @deprecated Since 2.16. Use {@link #_enumDefault(AnnotationIntrospector, AnnotatedClass, Enum[])} instead. */ @Deprecated protected static Enum _enumDefault(AnnotationIntrospector intr, Class enumCls) { return (intr != null) ? intr.findDefaultEnumValue(_enumClass(enumCls)) : null; } protected static boolean _isIntType(Class erasedType) { if (erasedType.isPrimitive()) { erasedType = ClassUtil.wrapperType(erasedType); } return (erasedType == Long.class) || (erasedType == Integer.class) || (erasedType == Short.class) || (erasedType == Byte.class) ; } /* /********************************************************************** /* Public API /********************************************************************** */ public Enum findEnum(final String key) { Enum en = _enumsById.get(key); if (en == null) { if (_isIgnoreCase) { return _findEnumCaseInsensitive(key); } } return en; } // @since 2.12 protected Enum _findEnumCaseInsensitive(final String key) { for (Map.Entry> entry : _enumsById.entrySet()) { if (key.equalsIgnoreCase(entry.getKey())) { return entry.getValue(); } } return null; } public Enum getEnum(int index) { if (index < 0 || index >= _enums.length) { return null; } return _enums[index]; } public Enum getDefaultValue(){ return _defaultValue; } public Enum[] getRawEnums() { return _enums; } public List> getEnums() { ArrayList> enums = new ArrayList>(_enums.length); for (Enum e : _enums) { enums.add(e); } return enums; } /** * @since 2.7.3 */ public Collection getEnumIds() { return _enumsById.keySet(); } public Class> getEnumClass() { return _enumClass; } public int lastValidIndex() { return _enums.length-1; } /** * Accessor for checking if we have a special case in which value to map * is from {@code @JsonValue} annotated accessor with integral type: this * matters for cases where incoming content value is of integral type * and should be mapped to specific value and NOT to {@code Enum.index()}. * * @since 2.13 */ public boolean isFromIntValue() { return _isFromIntValue; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy