package com.fasterxml.jackson.databind.util; import com.fasterxml.jackson.databind.AnnotationIntrospector; import java.lang.reflect.Method; import java.util.*; /** * Helper class used to resolve String values (either JSON Object field * names or regular String values) into Java Enum instances. */ public class EnumResolver<T extends Enum<T>> { protected final Class<T> _enumClass; protected final T[] _enums; protected final HashMap<String, T> _enumsById; protected EnumResolver(Class<T> enumClass, T[] enums, HashMap<String, T> map) { _enumClass = enumClass; _enums = enums; _enumsById = map; } /** * Factory method for constructing resolver that maps from Enum.name() into * Enum value */ public static <ET extends Enum<ET>> EnumResolver<ET> constructFor(Class<ET> enumCls, AnnotationIntrospector ai) { ET[] enumValues = enumCls.getEnumConstants(); if (enumValues == null) { throw new IllegalArgumentException("No enum constants for class "+enumCls.getName()); } HashMap<String, ET> map = new HashMap<String, ET>(); for (ET e : enumValues) { map.put(ai.findEnumValue(e), e); } return new EnumResolver<ET>(enumCls, enumValues, map); } /** * Factory method for constructing resolver that maps from Enum.toString() into * Enum value */ public static <ET extends Enum<ET>> EnumResolver<ET> constructUsingToString(Class<ET> enumCls) { ET[] enumValues = enumCls.getEnumConstants(); HashMap<String, ET> map = new HashMap<String, ET>(); // from last to first, so that in case of duplicate values, first wins for (int i = enumValues.length; --i >= 0; ) { ET e = enumValues[i]; map.put(e.toString(), e); } return new EnumResolver<ET>(enumCls, enumValues, map); } public static <ET extends Enum<ET>> EnumResolver<ET> constructUsingMethod(Class<ET> enumCls, Method accessor) { ET[] enumValues = enumCls.getEnumConstants(); HashMap<String, ET> map = new HashMap<String, ET>(); // from last to first, so that in case of duplicate values, first wins for (int i = enumValues.length; --i >= 0; ) { ET en = enumValues[i]; try { Object o = accessor.invoke(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<ET>(enumCls, enumValues, map); } /** * This method is needed because of the dynamic nature of constructing Enum * resolvers. */ @SuppressWarnings({ "unchecked", "rawtypes" }) 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); } /** * Method that needs to be used instead of {@link #constructUsingToString} * if static type of enum is not known. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static EnumResolver<?> constructUnsafeUsingToString(Class<?> rawEnumCls) { // oh so wrong... not much that can be done tho Class<Enum> enumCls = (Class<Enum>) rawEnumCls; return constructUsingToString(enumCls); } /** * Method used when actual String serialization is indicated using @JsonValue * on a method. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static EnumResolver<?> constructUnsafeUsingMethod(Class<?> rawEnumCls, Method accessor) { // wrong as ever but: Class<Enum> enumCls = (Class<Enum>) rawEnumCls; return constructUsingMethod(enumCls, accessor); } public T findEnum(String key) { return _enumsById.get(key); } public T getEnum(int index) { if (index < 0 || index >= _enums.length) { return null; } return _enums[index]; } public Class<T> getEnumClass() { return _enumClass; } public int lastValidIndex() { return _enums.length-1; } }