blob: c3ad93ef81a516f3541aed8f91b925bf7bab9734 [file] [log] [blame]
package com.fasterxml.jackson.databind.deser.std;
import java.io.IOException;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.CompactStringObjectMap;
import com.fasterxml.jackson.databind.util.EnumResolver;
/**
* Deserializer class that can deserialize instances of
* specified Enum class from Strings and Integers.
*/
@JacksonStdImpl // was missing until 2.6
public class EnumDeserializer
extends StdScalarDeserializer<Object>
{
private static final long serialVersionUID = 1L;
protected Object[] _enumsByIndex;
/**
* @since 2.8
*/
private final Enum<?> _enumDefaultValue;
/**
* @since 2.7.3
*/
protected final CompactStringObjectMap _lookupByName;
/**
* Alternatively, we may need a different lookup object if "use toString"
* is defined.
*
* @since 2.7.3
*/
protected CompactStringObjectMap _lookupByToString;
public EnumDeserializer(EnumResolver byNameResolver)
{
super(byNameResolver.getEnumClass());
_lookupByName = byNameResolver.constructLookup();
_enumsByIndex = byNameResolver.getRawEnums();
_enumDefaultValue = byNameResolver.getDefaultValue();
}
/**
* @deprecated Since 2.8
*/
@Deprecated
public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config,
Class<?> enumClass, AnnotatedMethod factory) {
return deserializerForCreator(config, enumClass, factory, null, null);
}
/**
* Factory method used when Enum instances are to be deserialized
* using a creator (static factory method)
*
* @return Deserializer based on given factory method
*
* @since 2.8
*/
public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config,
Class<?> enumClass, AnnotatedMethod factory,
ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps)
{
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(factory.getMember(),
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
return new FactoryBasedEnumDeserializer(enumClass, factory,
factory.getParameterType(0),
valueInstantiator, creatorProps);
}
/**
* 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
*
* @since 2.8
*/
public static JsonDeserializer<?> deserializerForNoArgsCreator(DeserializationConfig config,
Class<?> enumClass, AnnotatedMethod factory)
{
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(factory.getMember(),
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
return new FactoryBasedEnumDeserializer(enumClass, factory);
}
/*
/**********************************************************
/* Default JsonDeserializer implementation
/**********************************************************
*/
/**
* Because of costs associated with constructing Enum resolvers,
* let's cache instances by default.
*/
@Override
public boolean isCachable() { return true; }
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
JsonToken curr = p.getCurrentToken();
// 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)
? _getToStringLookup(ctxt) : _lookupByName;
final String name = p.getText();
Object result = lookup.find(name);
if (result == null) {
return _deserializeAltString(p, ctxt, lookup, name);
}
return result;
}
// But let's consider int acceptable as well (if within ordinal range)
if (curr == JsonToken.VALUE_NUMBER_INT) {
// ... unless told not to do that
int index = p.getIntValue();
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
return ctxt.handleWeirdNumberValue(_enumClass(), index,
"not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow"
);
}
if (index >= 0 && index < _enumsByIndex.length) {
return _enumsByIndex[index];
}
if ((_enumDefaultValue != null)
&& ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
return _enumDefaultValue;
}
if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
return ctxt.handleWeirdNumberValue(_enumClass(), index,
"index value outside legal index range [0..%s]",
_enumsByIndex.length-1);
}
return null;
}
return _deserializeOther(p, ctxt);
}
/*
/**********************************************************
/* 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)) {
return getEmptyValue(ctxt);
}
} else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
// [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above)
char c = name.charAt(0);
if (c >= '0' && c <= '9') {
try {
int index = Integer.parseInt(name);
if (index >= 0 && index < _enumsByIndex.length) {
return _enumsByIndex[index];
}
} catch (NumberFormatException e) {
// fine, ignore, was not an integer
}
}
}
if ((_enumDefaultValue != null)
&& ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
return _enumDefaultValue;
}
if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
return ctxt.handleWeirdStringValue(_enumClass(), name,
"value not one of declared Enum instance names: %s", lookup.keys());
}
return null;
}
protected Object _deserializeOther(JsonParser p, DeserializationContext ctxt) throws IOException
{
// [databind#381]
if (p.hasToken(JsonToken.START_ARRAY)) {
return _deserializeFromArray(p, ctxt);
}
return ctxt.handleUnexpectedToken(_enumClass(), p);
}
protected Class<?> _enumClass() {
return handledType();
}
protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt)
{
CompactStringObjectMap lookup = _lookupByToString;
// note: exact locking not needed; all we care for here is to try to
// reduce contention for the initial resolution
if (lookup == null) {
synchronized (this) {
lookup = EnumResolver.constructUnsafeUsingToString(_enumClass(),
ctxt.getAnnotationIntrospector())
.constructLookup();
}
_lookupByToString = lookup;
}
return lookup;
}
}