1 package com.fasterxml.jackson.databind.util; 2 3 import java.util.*; 4 5 import com.fasterxml.jackson.databind.AnnotationIntrospector; 6 import com.fasterxml.jackson.databind.introspect.AnnotatedMember; 7 8 /** 9 * Helper class used to resolve String values (either JSON Object field 10 * names or regular String values) into Java Enum instances. 11 */ 12 public class EnumResolver implements java.io.Serializable 13 { 14 private static final long serialVersionUID = 1L; 15 16 protected final Class<Enum<?>> _enumClass; 17 18 protected final Enum<?>[] _enums; 19 20 protected final HashMap<String, Enum<?>> _enumsById; 21 22 protected final Enum<?> _defaultValue; 23 EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums, HashMap<String, Enum<?>> map, Enum<?> defaultValue)24 protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums, 25 HashMap<String, Enum<?>> map, Enum<?> defaultValue) 26 { 27 _enumClass = enumClass; 28 _enums = enums; 29 _enumsById = map; 30 _defaultValue = defaultValue; 31 } 32 33 /** 34 * Factory method for constructing resolver that maps from Enum.name() into 35 * Enum value 36 */ constructFor(Class<Enum<?>> enumCls, AnnotationIntrospector ai)37 public static EnumResolver constructFor(Class<Enum<?>> enumCls, AnnotationIntrospector ai) 38 { 39 Enum<?>[] enumValues = enumCls.getEnumConstants(); 40 if (enumValues == null) { 41 throw new IllegalArgumentException("No enum constants for class "+enumCls.getName()); 42 } 43 String[] names = ai.findEnumValues(enumCls, enumValues, new String[enumValues.length]); 44 final String[][] allAliases = new String[names.length][]; 45 ai.findEnumAliases(enumCls, enumValues, allAliases); 46 HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>(); 47 for (int i = 0, len = enumValues.length; i < len; ++i) { 48 final Enum<?> enumValue = enumValues[i]; 49 String name = names[i]; 50 if (name == null) { 51 name = enumValue.name(); 52 } 53 map.put(name, enumValue); 54 String[] aliases = allAliases[i]; 55 if (aliases != null) { 56 for (String alias : aliases) { 57 // TODO: JDK 1.8, use Map.putIfAbsent() 58 // Avoid overriding any primary names 59 if (!map.containsKey(alias)) { 60 map.put(alias, enumValue); 61 } 62 } 63 } 64 } 65 return new EnumResolver(enumCls, enumValues, map, ai.findDefaultEnumValue(enumCls)); 66 } 67 68 /** 69 * @deprecated Since 2.8, use {@link #constructUsingToString(Class, AnnotationIntrospector)} instead 70 */ 71 @Deprecated constructUsingToString(Class<Enum<?>> enumCls)72 public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls) { 73 return constructUsingToString(enumCls, null); 74 } 75 76 /** 77 * Factory method for constructing resolver that maps from Enum.toString() into 78 * Enum value 79 * 80 * @since 2.8 81 */ constructUsingToString(Class<Enum<?>> enumCls, AnnotationIntrospector ai)82 public static EnumResolver constructUsingToString(Class<Enum<?>> enumCls, 83 AnnotationIntrospector ai) 84 { 85 Enum<?>[] enumConstants = enumCls.getEnumConstants(); 86 HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>(); 87 final String[][] allAliases = new String[enumConstants.length][]; 88 ai.findEnumAliases(enumCls, enumConstants, allAliases); 89 90 // from last to first, so that in case of duplicate values, first wins 91 for (int i = enumConstants.length; --i >= 0; ) { 92 Enum<?> enumValue = enumConstants[i]; 93 map.put(enumValue.toString(), enumValue); 94 String[] aliases = allAliases[i]; 95 if (aliases != null) { 96 for (String alias : aliases) { 97 // TODO: JDK 1.8, use Map.putIfAbsent() 98 // Avoid overriding any primary names 99 if (!map.containsKey(alias)) { 100 map.put(alias, enumValue); 101 } 102 } 103 } 104 } 105 return new EnumResolver(enumCls, enumConstants, map, ai.findDefaultEnumValue(enumCls)); 106 } 107 108 /** 109 * @since 2.9 110 */ constructUsingMethod(Class<Enum<?>> enumCls, AnnotatedMember accessor, AnnotationIntrospector ai)111 public static EnumResolver constructUsingMethod(Class<Enum<?>> enumCls, 112 AnnotatedMember accessor, 113 AnnotationIntrospector ai) 114 { 115 Enum<?>[] enumValues = enumCls.getEnumConstants(); 116 HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>(); 117 // from last to first, so that in case of duplicate values, first wins 118 for (int i = enumValues.length; --i >= 0; ) { 119 Enum<?> en = enumValues[i]; 120 try { 121 Object o = accessor.getValue(en); 122 if (o != null) { 123 map.put(o.toString(), en); 124 } 125 } catch (Exception e) { 126 throw new IllegalArgumentException("Failed to access @JsonValue of Enum value "+en+": "+e.getMessage()); 127 } 128 } 129 Enum<?> defaultEnum = (ai != null) ? ai.findDefaultEnumValue(enumCls) : null; 130 return new EnumResolver(enumCls, enumValues, map, defaultEnum); 131 } 132 133 /** 134 * This method is needed because of the dynamic nature of constructing Enum 135 * resolvers. 136 */ 137 @SuppressWarnings({ "unchecked" }) constructUnsafe(Class<?> rawEnumCls, AnnotationIntrospector ai)138 public static EnumResolver constructUnsafe(Class<?> rawEnumCls, AnnotationIntrospector ai) 139 { 140 /* This is oh so wrong... but at least ugliness is mostly hidden in just 141 * this one place. 142 */ 143 Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls; 144 return constructFor(enumCls, ai); 145 } 146 147 /** 148 * Method that needs to be used instead of {@link #constructUsingToString} 149 * if static type of enum is not known. 150 * 151 * @since 2.8 152 */ 153 @SuppressWarnings({ "unchecked" }) constructUnsafeUsingToString(Class<?> rawEnumCls, AnnotationIntrospector ai)154 public static EnumResolver constructUnsafeUsingToString(Class<?> rawEnumCls, 155 AnnotationIntrospector ai) 156 { 157 // oh so wrong... not much that can be done tho 158 Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls; 159 return constructUsingToString(enumCls, ai); 160 } 161 162 /** 163 * Method used when actual String serialization is indicated using @JsonValue 164 * on a method. 165 * 166 * @since 2.9 167 */ 168 @SuppressWarnings({ "unchecked" }) constructUnsafeUsingMethod(Class<?> rawEnumCls, AnnotatedMember accessor, AnnotationIntrospector ai)169 public static EnumResolver constructUnsafeUsingMethod(Class<?> rawEnumCls, 170 AnnotatedMember accessor, 171 AnnotationIntrospector ai) 172 { 173 // wrong as ever but: 174 Class<Enum<?>> enumCls = (Class<Enum<?>>) rawEnumCls; 175 return constructUsingMethod(enumCls, accessor, ai); 176 } 177 constructLookup()178 public CompactStringObjectMap constructLookup() { 179 return CompactStringObjectMap.construct(_enumsById); 180 } 181 findEnum(String key)182 public Enum<?> findEnum(String key) { return _enumsById.get(key); } 183 getEnum(int index)184 public Enum<?> getEnum(int index) { 185 if (index < 0 || index >= _enums.length) { 186 return null; 187 } 188 return _enums[index]; 189 } 190 getDefaultValue()191 public Enum<?> getDefaultValue(){ 192 return _defaultValue; 193 } 194 getRawEnums()195 public Enum<?>[] getRawEnums() { 196 return _enums; 197 } 198 getEnums()199 public List<Enum<?>> getEnums() { 200 ArrayList<Enum<?>> enums = new ArrayList<Enum<?>>(_enums.length); 201 for (Enum<?> e : _enums) { 202 enums.add(e); 203 } 204 return enums; 205 } 206 207 /** 208 * @since 2.7.3 209 */ getEnumIds()210 public Collection<String> getEnumIds() { 211 return _enumsById.keySet(); 212 } 213 getEnumClass()214 public Class<Enum<?>> getEnumClass() { return _enumClass; } 215 lastValidIndex()216 public int lastValidIndex() { return _enums.length-1; } 217 } 218 219