package com.amazonaws.services.dynamodbv2.json.converter.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.amazonaws.services.dynamodbv2.json.converter.JacksonStreamReader;
import com.amazonaws.services.dynamodbv2.json.converter.JacksonStreamReaderException;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
/**
* Implementation of JacksonStreamReader transformer.
*/
public class JacksonStreamReaderImpl implements JacksonStreamReader {
/**
* JsonParser for getting tokens.
*/
private final JsonParser jp;
/**
* Constructs a {@link JacksonStreamReaderImpl} with the provided {@link JsonParser}.
*
* @param jp
* JsonParser from which to get tokens
* @throws IOException
* Null JsonParser or error getting token
*/
public JacksonStreamReaderImpl(final JsonParser jp) throws IOException {
if (jp == null) {
throw new JacksonStreamReaderException("JsonParser cannot be null", JsonLocation.NA);
}
this.jp = jp;
if (jp.getCurrentToken() == null) {
jp.nextToken();
}
}
/**
* {@inheritDoc}
*/
@Override
public Map<String, AttributeValue> getNextItem() throws IOException {
if (isEndReached()) {
return null;
}
if (isObject()) {
return getNextMap();
} else if (isArray() || isEndOfArray() || isEndOfObject()) {
jp.nextToken();
return getNextItem();
} else {
throw new JacksonStreamReaderException("The start of next item needs to be an object, but was "
+ jp.getCurrentToken(), jp.getCurrentLocation());
}
}
/**
* Gets the next list from the JsonParser in a DynamoDB representation.
*
* @return DynamoDB representation of the next list from the JsonParser
* @throws IOException
* Error getting token or unknown value type.
*/
private List<AttributeValue> getNextList() throws IOException {
final List<AttributeValue> list = new ArrayList<AttributeValue>();
while (JsonToken.END_ARRAY != jp.nextToken()) {
switch (jp.getCurrentToken()) {
case VALUE_STRING:
list.add(new AttributeValue().withS(jp.getText()));
break;
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
list.add(new AttributeValue().withN(jp.getValueAsString()));
break;
case VALUE_TRUE:
case VALUE_FALSE:
list.add(new AttributeValue().withBOOL(jp.getBooleanValue()));
break;
case VALUE_NULL:
list.add(new AttributeValue().withNULL(true));
break;
case START_OBJECT:
list.add(new AttributeValue().withM(getNextMap()));
break;
case START_ARRAY:
list.add(new AttributeValue().withL(getNextList()));
break;
default:
throw new JacksonStreamReaderException("Unknown value type: " + jp.getCurrentToken().name(),
jp.getCurrentLocation());
}
}
return list;
}
/**
* Gets the next map from the JsonParser in a DynamoDB representation.
*
* @return DynamoDB representation of the next map from the JsonParser
* @throws IOException
* Error getting token or unknown value type
*/
private Map<String, AttributeValue> getNextMap() throws IOException {
final Map<String, AttributeValue> map = new HashMap<String, AttributeValue>();
while (JsonToken.END_OBJECT != jp.nextToken()) {
switch (jp.getCurrentToken()) {
case FIELD_NAME:
continue;
case VALUE_STRING:
map.put(jp.getCurrentName(), new AttributeValue().withS(jp.getText()));
break;
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
map.put(jp.getCurrentName(), new AttributeValue().withN(jp.getValueAsString()));
break;
case VALUE_TRUE:
case VALUE_FALSE:
map.put(jp.getCurrentName(), new AttributeValue().withBOOL(jp.getBooleanValue()));
break;
case VALUE_NULL:
map.put(jp.getCurrentName(), new AttributeValue().withNULL(true));
break;
case START_OBJECT:
map.put(jp.getCurrentName(), new AttributeValue().withM(getNextMap()));
break;
case START_ARRAY:
map.put(jp.getCurrentName(), new AttributeValue().withL(getNextList()));
break;
default:
throw new JacksonStreamReaderException("Unknown value type: " + jp.getCurrentToken().name(),
jp.getCurrentLocation());
}
}
return map;
}
/**
* Checks if current token is the start of an array.
*
* @return True if current token is the start of an array.
*/
private boolean isArray() {
return jp.getCurrentToken() == JsonToken.START_ARRAY;
}
/**
* Checks if the current token is the end of an array.
*
* @return True if the current token is the end of an array
*/
private boolean isEndOfArray() {
return jp.getCurrentToken() == JsonToken.END_ARRAY;
}
/**
* Checks if the current token is the end of an object.
*
* @return True if the current token is the end of an object
*/
private boolean isEndOfObject() {
return jp.getCurrentToken() == JsonToken.END_OBJECT;
}
/**
* Checks if the current token is null.
*
* @return True if the current token is null
*/
private boolean isEndReached() {
return jp.getCurrentToken() == null;
}
/**
* Checks if the current token is the start of an object.
*
* @return True if the current token is the start of an object
*/
private boolean isObject() {
return jp.getCurrentToken() == JsonToken.START_OBJECT;
}
/**
* {@inheritDoc}
*/
@Override
public boolean seek(final String fieldName) throws IOException {
if (fieldName == null) {
return false;
}
do {
if (fieldName.equals(jp.getCurrentName())) {
jp.nextValue();
return true;
}
jp.nextToken();
} while (!isEndReached());
return false;
}
}