package com.fasterxml.jackson.databind.deser.std; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.*; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.io.NumberInput; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.EnumResolver; import com.fasterxml.jackson.databind.util.TokenBuffer; /** * Default {@link KeyDeserializer} implementation used for most {@link java.util.Map} * types Jackson supports. * Implemented as "chameleon" (or swiss pocket knife) class; not particularly elegant, * but helps reduce number of classes and jar size (class metadata adds significant * per-class overhead; much more than bytecode). */ @JacksonStdImpl public class StdKeyDeserializer extends KeyDeserializer implements java.io.Serializable { private static final long serialVersionUID = 1L; public final static int TYPE_BOOLEAN = 1; public final static int TYPE_BYTE = 2; public final static int TYPE_SHORT = 3; public final static int TYPE_CHAR = 4; public final static int TYPE_INT = 5; public final static int TYPE_LONG = 6; public final static int TYPE_FLOAT = 7; public final static int TYPE_DOUBLE = 8; public final static int TYPE_LOCALE = 9; public final static int TYPE_DATE = 10; public final static int TYPE_CALENDAR = 11; public final static int TYPE_UUID = 12; public final static int TYPE_URI = 13; public final static int TYPE_URL = 14; public final static int TYPE_CLASS = 15; public final static int TYPE_CURRENCY = 16; public final static int TYPE_BYTE_ARRAY = 17; // since 2.9 final protected int _kind; final protected Class<?> _keyClass; /** * Some types that are deserialized using a helper deserializer. */ protected final FromStringDeserializer<?> _deser; protected StdKeyDeserializer(int kind, Class<?> cls) { this(kind, cls, null); } protected StdKeyDeserializer(int kind, Class<?> cls, FromStringDeserializer<?> deser) { _kind = kind; _keyClass = cls; _deser = deser; } public static StdKeyDeserializer forType(Class<?> raw) { int kind; // first common types: if (raw == String.class || raw == Object.class || raw == CharSequence.class) { return StringKD.forType(raw); } else if (raw == UUID.class) { kind = TYPE_UUID; } else if (raw == Integer.class) { kind = TYPE_INT; } else if (raw == Long.class) { kind = TYPE_LONG; } else if (raw == Date.class) { kind = TYPE_DATE; } else if (raw == Calendar.class) { kind = TYPE_CALENDAR; // then less common ones... } else if (raw == Boolean.class) { kind = TYPE_BOOLEAN; } else if (raw == Byte.class) { kind = TYPE_BYTE; } else if (raw == Character.class) { kind = TYPE_CHAR; } else if (raw == Short.class) { kind = TYPE_SHORT; } else if (raw == Float.class) { kind = TYPE_FLOAT; } else if (raw == Double.class) { kind = TYPE_DOUBLE; } else if (raw == URI.class) { kind = TYPE_URI; } else if (raw == URL.class) { kind = TYPE_URL; } else if (raw == Class.class) { kind = TYPE_CLASS; } else if (raw == Locale.class) { FromStringDeserializer<?> deser = FromStringDeserializer.findDeserializer(Locale.class); return new StdKeyDeserializer(TYPE_LOCALE, raw, deser); } else if (raw == Currency.class) { FromStringDeserializer<?> deser = FromStringDeserializer.findDeserializer(Currency.class); return new StdKeyDeserializer(TYPE_CURRENCY, raw, deser); } else if (raw == byte[].class) { kind = TYPE_BYTE_ARRAY; } else { return null; } return new StdKeyDeserializer(kind, raw); } @Override public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { if (key == null) { // is this even legal call? return null; } try { Object result = _parse(key, ctxt); if (result != null) { return result; } } catch (Exception re) { return ctxt.handleWeirdKey(_keyClass, key, "not a valid representation, problem: (%s) %s", re.getClass().getName(), re.getMessage()); } if (_keyClass.isEnum() && ctxt.getConfig().isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { return null; } return ctxt.handleWeirdKey(_keyClass, key, "not a valid representation"); } public Class<?> getKeyClass() { return _keyClass; } protected Object _parse(String key, DeserializationContext ctxt) throws Exception { switch (_kind) { case TYPE_BOOLEAN: if ("true".equals(key)) { return Boolean.TRUE; } if ("false".equals(key)) { return Boolean.FALSE; } return ctxt.handleWeirdKey(_keyClass, key, "value not 'true' or 'false'"); case TYPE_BYTE: { int value = _parseInt(key); // allow range up to 255, inclusive (to support "unsigned" byte) if (value < Byte.MIN_VALUE || value > 255) { return ctxt.handleWeirdKey(_keyClass, key, "overflow, value can not be represented as 8-bit value"); } return Byte.valueOf((byte) value); } case TYPE_SHORT: { int value = _parseInt(key); if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { return ctxt.handleWeirdKey(_keyClass, key, "overflow, value can not be represented as 16-bit value"); // fall-through and truncate if need be } return Short.valueOf((short) value); } case TYPE_CHAR: if (key.length() == 1) { return Character.valueOf(key.charAt(0)); } return ctxt.handleWeirdKey(_keyClass, key, "can only convert 1-character Strings"); case TYPE_INT: return _parseInt(key); case TYPE_LONG: return _parseLong(key); case TYPE_FLOAT: // Bounds/range checks would be tricky here, so let's not bother even trying... return Float.valueOf((float) _parseDouble(key)); case TYPE_DOUBLE: return _parseDouble(key); case TYPE_LOCALE: try { return _deser._deserialize(key, ctxt); } catch (IllegalArgumentException e) { return _weirdKey(ctxt, key, e); } case TYPE_CURRENCY: try { return _deser._deserialize(key, ctxt); } catch (IllegalArgumentException e) { return _weirdKey(ctxt, key, e); } case TYPE_DATE: return ctxt.parseDate(key); case TYPE_CALENDAR: return ctxt.constructCalendar(ctxt.parseDate(key)); case TYPE_UUID: try { return UUID.fromString(key); } catch (Exception e) { return _weirdKey(ctxt, key, e); } case TYPE_URI: try { return URI.create(key); } catch (Exception e) { return _weirdKey(ctxt, key, e); } case TYPE_URL: try { return new URL(key); } catch (MalformedURLException e) { return _weirdKey(ctxt, key, e); } case TYPE_CLASS: try { return ctxt.findClass(key); } catch (Exception e) { return ctxt.handleWeirdKey(_keyClass, key, "unable to parse key as Class"); } case TYPE_BYTE_ARRAY: try { return ctxt.getConfig().getBase64Variant().decode(key); } catch (IllegalArgumentException e) { return _weirdKey(ctxt, key, e); } default: throw new IllegalStateException("Internal error: unknown key type "+_keyClass); } } /* /********************************************************** /* Helper methods for sub-classes /********************************************************** */ protected int _parseInt(String key) throws IllegalArgumentException { return Integer.parseInt(key); } protected long _parseLong(String key) throws IllegalArgumentException { return Long.parseLong(key); } protected double _parseDouble(String key) throws IllegalArgumentException { return NumberInput.parseDouble(key); } // @since 2.9 protected Object _weirdKey(DeserializationContext ctxt, String key, Exception e) throws IOException { return ctxt.handleWeirdKey(_keyClass, key, "problem: %s", e.getMessage()); } /* /********************************************************** /* First: the standard "String as String" deserializer /********************************************************** */ @JacksonStdImpl final static class StringKD extends StdKeyDeserializer { private static final long serialVersionUID = 1L; private final static StringKD sString = new StringKD(String.class); private final static StringKD sObject = new StringKD(Object.class); private StringKD(Class<?> nominalType) { super(-1, nominalType); } public static StringKD forType(Class<?> nominalType) { if (nominalType == String.class) { return sString; } if (nominalType == Object.class) { return sObject; } return new StringKD(nominalType); } @Override public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException { return key; } } /* /********************************************************** /* Key deserializer implementations; other /********************************************************** */ /** * Key deserializer that wraps a "regular" deserializer (but one * that must recognize FIELD_NAMEs as text!) to reuse existing * handlers as key handlers. */ final static class DelegatingKD extends KeyDeserializer // note: NOT the std one implements java.io.Serializable { private static final long serialVersionUID = 1L; final protected Class<?> _keyClass; protected final JsonDeserializer<?> _delegate; protected DelegatingKD(Class<?> cls, JsonDeserializer<?> deser) { _keyClass = cls; _delegate = deser; } @SuppressWarnings("resource") @Override public final Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { if (key == null) { // is this even legal call? return null; } TokenBuffer tb = new TokenBuffer(ctxt.getParser(), ctxt); tb.writeString(key); try { // Ugh... should not have to give parser which may or may not be correct one... JsonParser p = tb.asParser(); p.nextToken(); Object result = _delegate.deserialize(p, ctxt); if (result != null) { return result; } return ctxt.handleWeirdKey(_keyClass, key, "not a valid representation"); } catch (Exception re) { return ctxt.handleWeirdKey(_keyClass, key, "not a valid representation: %s", re.getMessage()); } } public Class<?> getKeyClass() { return _keyClass; } } @JacksonStdImpl final static class EnumKD extends StdKeyDeserializer { private static final long serialVersionUID = 1L; protected final EnumResolver _byNameResolver; protected final AnnotatedMethod _factory; /** * Lazily constructed alternative in case there is need to * use 'toString()' method as the source. * * @since 2.7.3 */ protected EnumResolver _byToStringResolver; protected EnumKD(EnumResolver er, AnnotatedMethod factory) { super(-1, er.getEnumClass()); _byNameResolver = er; _factory = factory; } @Override public Object _parse(String key, DeserializationContext ctxt) throws IOException { if (_factory != null) { try { return _factory.call1(key); } catch (Exception e) { ClassUtil.unwrapAndThrowAsIAE(e); } } EnumResolver res = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING) ? _getToStringResolver(ctxt) : _byNameResolver; Enum<?> e = res.findEnum(key); if ((e == null) && !ctxt.getConfig().isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { return ctxt.handleWeirdKey(_keyClass, key, "not one of values excepted for Enum class: %s", res.getEnumIds()); // fall-through if problems are collected, not immediately thrown } return e; } private EnumResolver _getToStringResolver(DeserializationContext ctxt) { EnumResolver res = _byToStringResolver; if (res == null) { synchronized (this) { res = EnumResolver.constructUnsafeUsingToString(_byNameResolver.getEnumClass(), ctxt.getAnnotationIntrospector()); } } return res; } } /** * Key deserializer that calls a single-string-arg constructor * to instantiate desired key type. */ final static class StringCtorKeyDeserializer extends StdKeyDeserializer { private static final long serialVersionUID = 1L; protected final Constructor<?> _ctor; public StringCtorKeyDeserializer(Constructor<?> ctor) { super(-1, ctor.getDeclaringClass()); _ctor = ctor; } @Override public Object _parse(String key, DeserializationContext ctxt) throws Exception { return _ctor.newInstance(key); } } /** * Key deserializer that calls a static no-args factory method * to instantiate desired key type. */ final static class StringFactoryKeyDeserializer extends StdKeyDeserializer { private static final long serialVersionUID = 1L; final Method _factoryMethod; public StringFactoryKeyDeserializer(Method fm) { super(-1, fm.getDeclaringClass()); _factoryMethod = fm; } @Override public Object _parse(String key, DeserializationContext ctxt) throws Exception { return _factoryMethod.invoke(null, key); } } }