package com.fasterxml.jackson.databind.deser.std;
import java.io.IOException;
import java.util.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.type.*;
import com.fasterxml.jackson.databind.util.ArrayBuilders;
import com.fasterxml.jackson.databind.util.ObjectBuffer;
/**
* Container for deserializers used for instantiating "primitive arrays",
* arrays that contain non-object java primitive types.
*/
public class PrimitiveArrayDeserializers
{
HashMap<JavaType,JsonDeserializer<Object>> _allDeserializers;
final static PrimitiveArrayDeserializers instance = new PrimitiveArrayDeserializers();
protected PrimitiveArrayDeserializers()
{
_allDeserializers = new HashMap<JavaType,JsonDeserializer<Object>>();
// note: we'll use component type as key, not array type
add(boolean.class, new BooleanDeser());
/* ByteDeser is bit special, as it has 2 separate modes of operation;
* one for String input (-> base64 input), the other for
* numeric input
*/
add(byte.class, new ByteDeser());
add(short.class, new ShortDeser());
add(int.class, new IntDeser());
add(long.class, new LongDeser());
add(float.class, new FloatDeser());
add(double.class, new DoubleDeser());
add(String.class, new StringDeser());
/* also: char[] is most likely only used with Strings; doesn't
* seem to make sense to transfer as numbers
*/
add(char.class, new CharDeser());
}
public static HashMap<JavaType,JsonDeserializer<Object>> getAll()
{
return instance._allDeserializers;
}
@SuppressWarnings("unchecked")
private void add(Class<?> cls, JsonDeserializer<?> deser)
{
/* Not super clean to use default TypeFactory in general, but
* since primitive array types can't be modified for anything
* useful, this should be ok:
*/
_allDeserializers.put(TypeFactory.defaultInstance().constructType(cls),
(JsonDeserializer<Object>) deser);
}
public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException
{
/* Should there be separate handling for base64 stuff?
* for now this should be enough:
*/
return typeDeserializer.deserializeTypedFromArray(jp, ctxt);
}
/*
/********************************************************
/* Intermediate base class
/********************************************************
*/
/**
* Intermediate base class for primitive array deserializers
*/
static abstract class Base<T> extends StdDeserializer<T>
{
private static final long serialVersionUID = 1L;
protected Base(Class<T> cls) {
super(cls);
}
@Override
public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException
{
return typeDeserializer.deserializeTypedFromArray(jp, ctxt);
}
}
/*
/********************************************************
/* Actual deserializers: efficient String[], char[] deserializers
/********************************************************
*/
@JacksonStdImpl
final static class StringDeser
extends Base<String[]>
{
private static final long serialVersionUID = 1L;
public StringDeser() { super(String[].class); }
@Override
public String[] deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// Ok: must point to START_ARRAY (or equivalent)
if (!jp.isExpectedStartArrayToken()) {
return handleNonArray(jp, ctxt);
}
final ObjectBuffer buffer = ctxt.leaseObjectBuffer();
Object[] chunk = buffer.resetAndStart();
int ix = 0;
JsonToken t;
while ((t = jp.nextToken()) != JsonToken.END_ARRAY) {
// Ok: no need to convert Strings, but must recognize nulls
String value = (t == JsonToken.VALUE_NULL) ? null : _parseString(jp, ctxt);
if (ix >= chunk.length) {
chunk = buffer.appendCompletedChunk(chunk);
ix = 0;
}
chunk[ix++] = value;
}
String[] result = buffer.completeAndClearBuffer(chunk, ix, String.class);
ctxt.returnObjectBuffer(buffer);
return result;
}
private final String[] handleNonArray(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// [JACKSON-526]: implicit arrays from single values?
if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
// [JACKSON-620] Empty String can become null...
if ((jp.getCurrentToken() == JsonToken.VALUE_STRING)
&& ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
String str = jp.getText();
if (str.length() == 0) {
return null;
}
}
throw ctxt.mappingException(_valueClass);
}
return new String[] { (jp.getCurrentToken() == JsonToken.VALUE_NULL) ? null : _parseString(jp, ctxt) };
}
}
@JacksonStdImpl
final static class CharDeser
extends Base<char[]>
{
private static final long serialVersionUID = 1L;
public CharDeser() { super(char[].class); }
@Override
public char[] deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
/* Won't take arrays, must get a String (could also
* convert other tokens to Strings... but let's not bother
* yet, doesn't seem to make sense)
*/
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_STRING) {
// note: can NOT return shared internal buffer, must copy:
char[] buffer = jp.getTextCharacters();
int offset = jp.getTextOffset();
int len = jp.getTextLength();
char[] result = new char[len];
System.arraycopy(buffer, offset, result, 0, len);
return result;
}
if (jp.isExpectedStartArrayToken()) {
// Let's actually build as a String, then get chars
StringBuilder sb = new StringBuilder(64);
while ((t = jp.nextToken()) != JsonToken.END_ARRAY) {
if (t != JsonToken.VALUE_STRING) {
throw ctxt.mappingException(Character.TYPE);
}
String str = jp.getText();
if (str.length() != 1) {
throw JsonMappingException.from(jp, "Can not convert a JSON String of length "+str.length()+" into a char element of char array");
}
sb.append(str.charAt(0));
}
return sb.toString().toCharArray();
}
// or, maybe an embedded object?
if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
Object ob = jp.getEmbeddedObject();
if (ob == null) return null;
if (ob instanceof char[]) {
return (char[]) ob;
}
if (ob instanceof String) {
return ((String) ob).toCharArray();
}
// 04-Feb-2011, tatu: byte[] can be converted; assuming base64 is wanted
if (ob instanceof byte[]) {
return Base64Variants.getDefaultVariant().encode((byte[]) ob, false).toCharArray();
}
// not recognized, just fall through
}
throw ctxt.mappingException(_valueClass);
}
}
/*
/********************************************************
/* Actual deserializers: primivate array desers
/********************************************************
*/
@JacksonStdImpl
final static class BooleanDeser
extends Base<boolean[]>
{
private static final long serialVersionUID = 1L;
public BooleanDeser() { super(boolean[].class); }
@Override
public boolean[] deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (!jp.isExpectedStartArrayToken()) {
return handleNonArray(jp, ctxt);
}
ArrayBuilders.BooleanBuilder builder = ctxt.getArrayBuilders().getBooleanBuilder();
boolean[] chunk = builder.resetAndStart();
int ix = 0;
while (jp.nextToken() != JsonToken.END_ARRAY) {
// whether we should allow truncating conversions?
boolean value = _parseBooleanPrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
}
chunk[ix++] = value;
}
return builder.completeAndClearBuffer(chunk, ix);
}
private final boolean[] handleNonArray(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// [JACKSON-620] Empty String can become null...
if ((jp.getCurrentToken() == JsonToken.VALUE_STRING)
&& ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
if (jp.getText().length() == 0) {
return null;
}
}
if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
throw ctxt.mappingException(_valueClass);
}
return new boolean[] { _parseBooleanPrimitive(jp, ctxt) };
}
}
/**
* When dealing with byte arrays we have one more alternative (compared
* to int/long/shorts): base64 encoded data.
*/
@JacksonStdImpl
final static class ByteDeser
extends Base<byte[]>
{
private static final long serialVersionUID = 1L;
public ByteDeser() { super(byte[].class); }
@Override
public byte[] deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
// Most likely case: base64 encoded String?
if (t == JsonToken.VALUE_STRING) {
return jp.getBinaryValue(ctxt.getBase64Variant());
}
// 31-Dec-2009, tatu: Also may be hidden as embedded Object
if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
Object ob = jp.getEmbeddedObject();
if (ob == null) return null;
if (ob instanceof byte[]) {
return (byte[]) ob;
}
}
if (!jp.isExpectedStartArrayToken()) {
return handleNonArray(jp, ctxt);
}
ArrayBuilders.ByteBuilder builder = ctxt.getArrayBuilders().getByteBuilder();
byte[] chunk = builder.resetAndStart();
int ix = 0;
while ((t = jp.nextToken()) != JsonToken.END_ARRAY) {
// whether we should allow truncating conversions?
byte value;
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) {
// should we catch overflow exceptions?
value = jp.getByteValue();
} else {
// [JACKSON-79]: should probably accept nulls as 0
if (t != JsonToken.VALUE_NULL) {
throw ctxt.mappingException(_valueClass.getComponentType());
}
value = (byte) 0;
}
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
}
chunk[ix++] = value;
}
return builder.completeAndClearBuffer(chunk, ix);
}
private final byte[] handleNonArray(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// [JACKSON-620] Empty String can become null...
if ((jp.getCurrentToken() == JsonToken.VALUE_STRING)
&& ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
if (jp.getText().length() == 0) {
return null;
}
}
if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
throw ctxt.mappingException(_valueClass);
}
byte value;
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) {
// should we catch overflow exceptions?
value = jp.getByteValue();
} else {
// [JACKSON-79]: should probably accept nulls as 'false'
if (t != JsonToken.VALUE_NULL) {
throw ctxt.mappingException(_valueClass.getComponentType());
}
value = (byte) 0;
}
return new byte[] { value };
}
}
@JacksonStdImpl
final static class ShortDeser
extends Base<short[]>
{
private static final long serialVersionUID = 1L;
public ShortDeser() { super(short[].class); }
@Override
public short[] deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (!jp.isExpectedStartArrayToken()) {
return handleNonArray(jp, ctxt);
}
ArrayBuilders.ShortBuilder builder = ctxt.getArrayBuilders().getShortBuilder();
short[] chunk = builder.resetAndStart();
int ix = 0;
while (jp.nextToken() != JsonToken.END_ARRAY) {
short value = _parseShortPrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
}
chunk[ix++] = value;
}
return builder.completeAndClearBuffer(chunk, ix);
}
private final short[] handleNonArray(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// [JACKSON-620] Empty String can become null...
if ((jp.getCurrentToken() == JsonToken.VALUE_STRING)
&& ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
if (jp.getText().length() == 0) {
return null;
}
}
if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
throw ctxt.mappingException(_valueClass);
}
return new short[] { _parseShortPrimitive(jp, ctxt) };
}
}
@JacksonStdImpl
final static class IntDeser
extends Base<int[]>
{
private static final long serialVersionUID = 1L;
public IntDeser() { super(int[].class); }
@Override
public int[] deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (!jp.isExpectedStartArrayToken()) {
return handleNonArray(jp, ctxt);
}
ArrayBuilders.IntBuilder builder = ctxt.getArrayBuilders().getIntBuilder();
int[] chunk = builder.resetAndStart();
int ix = 0;
while (jp.nextToken() != JsonToken.END_ARRAY) {
// whether we should allow truncating conversions?
int value = _parseIntPrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
}
chunk[ix++] = value;
}
return builder.completeAndClearBuffer(chunk, ix);
}
private final int[] handleNonArray(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// [JACKSON-620] Empty String can become null...
if ((jp.getCurrentToken() == JsonToken.VALUE_STRING)
&& ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
if (jp.getText().length() == 0) {
return null;
}
}
if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
throw ctxt.mappingException(_valueClass);
}
return new int[] { _parseIntPrimitive(jp, ctxt) };
}
}
@JacksonStdImpl
final static class LongDeser
extends Base<long[]>
{
private static final long serialVersionUID = 1L;
public LongDeser() { super(long[].class); }
@Override
public long[] deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (!jp.isExpectedStartArrayToken()) {
return handleNonArray(jp, ctxt);
}
ArrayBuilders.LongBuilder builder = ctxt.getArrayBuilders().getLongBuilder();
long[] chunk = builder.resetAndStart();
int ix = 0;
while (jp.nextToken() != JsonToken.END_ARRAY) {
long value = _parseLongPrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
}
chunk[ix++] = value;
}
return builder.completeAndClearBuffer(chunk, ix);
}
private final long[] handleNonArray(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// [JACKSON-620] Empty String can become null...
if ((jp.getCurrentToken() == JsonToken.VALUE_STRING)
&& ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
if (jp.getText().length() == 0) {
return null;
}
}
if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
throw ctxt.mappingException(_valueClass);
}
return new long[] { _parseLongPrimitive(jp, ctxt) };
}
}
@JacksonStdImpl
final static class FloatDeser
extends Base<float[]>
{
private static final long serialVersionUID = 1L;
public FloatDeser() { super(float[].class); }
@Override
public float[] deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (!jp.isExpectedStartArrayToken()) {
return handleNonArray(jp, ctxt);
}
ArrayBuilders.FloatBuilder builder = ctxt.getArrayBuilders().getFloatBuilder();
float[] chunk = builder.resetAndStart();
int ix = 0;
while (jp.nextToken() != JsonToken.END_ARRAY) {
// whether we should allow truncating conversions?
float value = _parseFloatPrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
}
chunk[ix++] = value;
}
return builder.completeAndClearBuffer(chunk, ix);
}
private final float[] handleNonArray(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// [JACKSON-620] Empty String can become null...
if ((jp.getCurrentToken() == JsonToken.VALUE_STRING)
&& ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
if (jp.getText().length() == 0) {
return null;
}
}
if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
throw ctxt.mappingException(_valueClass);
}
return new float[] { _parseFloatPrimitive(jp, ctxt) };
}
}
@JacksonStdImpl
final static class DoubleDeser
extends Base<double[]>
{
private static final long serialVersionUID = 1L;
public DoubleDeser() { super(double[].class); }
@Override
public double[] deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (!jp.isExpectedStartArrayToken()) {
return handleNonArray(jp, ctxt);
}
ArrayBuilders.DoubleBuilder builder = ctxt.getArrayBuilders().getDoubleBuilder();
double[] chunk = builder.resetAndStart();
int ix = 0;
while (jp.nextToken() != JsonToken.END_ARRAY) {
double value = _parseDoublePrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
}
chunk[ix++] = value;
}
return builder.completeAndClearBuffer(chunk, ix);
}
private final double[] handleNonArray(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// [JACKSON-620] Empty String can become null...
if ((jp.getCurrentToken() == JsonToken.VALUE_STRING)
&& ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
if (jp.getText().length() == 0) {
return null;
}
}
if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
throw ctxt.mappingException(_valueClass);
}
return new double[] { _parseDoublePrimitive(jp, ctxt) };
}
}
}