package ecologylab.serialization.deserializers.pullhandlers.stringformats;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Map;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import ecologylab.serialization.ClassDescriptor;
import ecologylab.serialization.DeserializationHookStrategy;
import ecologylab.serialization.FieldDescriptor;
import ecologylab.serialization.FieldType;
import ecologylab.serialization.SIMPLTranslationException;
import ecologylab.serialization.SimplTypesScope;
import ecologylab.serialization.TranslationContext;
import ecologylab.serialization.deserializers.pullhandlers.DeserializationProcedureState;
import ecologylab.serialization.types.element.IMappable;
/**
* JSON deserialization handler class. Uses the pull API for parsing the input JSON documents.
*
* @author nabeelshahzad
*
*/
public class JSONPullDeserializer extends StringPullDeserializer
{
/**
* This field is purely used for debugging; should be removed from production.
*/
@Deprecated
StringBuilder debugContext;
/**
* JsonParser object from the Jackson JSON parsing library. Implements a pull API for parsing JSON
*/
JsonParser jp = null;
public JSONPullDeserializer(SimplTypesScope translationScope,
TranslationContext translationContext, DeserializationHookStrategy deserializationHookStrategy)
{
super(translationScope, translationContext, deserializationHookStrategy);
}
public JSONPullDeserializer(SimplTypesScope translationScope,
TranslationContext translationContext)
{
super(translationScope, translationContext);
}
@Override
public Object parse(InputStream inputStream, Charset charSet) throws SIMPLTranslationException
{
try
{
configure(inputStream, charSet);
return parse();
}
catch (Exception ex)
{
throw new SIMPLTranslationException("exception occurred in deserialzation ", ex);
}
}
@Override
public Object parse(InputStream inputStream) throws SIMPLTranslationException
{
try
{
configure(inputStream);
return parse();
}
catch (Exception ex)
{
throw new SIMPLTranslationException("exception occurred in deserialzation ", ex);
}
}
/**
* The main parse method accepts a CharSequence and creates a corresponding object model. Sets up
* the root object and creates instances of the root object before calling a recursive method that
* creates the complete object model
*
* @param charSequence
* @return
* @throws SIMPLTranslationException
* @throws JsonParseException
* @throws IOException
* @throws SIMPLTranslationException
*/
@Override
public Object parse(CharSequence charSequence) throws SIMPLTranslationException
{
try
{
configure(charSequence);
return parse();
}
catch (Exception ex)
{
throw new SIMPLTranslationException("exception occurred in deserialzation ", ex);
}
}
private void configure(InputStream inputStream, Charset charSet) throws IOException, JsonParseException
{
// configure the json parser
JsonFactory f = new JsonFactory();
InputStreamReader tmpReader = new InputStreamReader(inputStream, charSet);
jp = f.createJsonParser(tmpReader);
}
private void configure(InputStream inputStream) throws IOException, JsonParseException
{
// configure the json parser
JsonFactory f = new JsonFactory();
jp = f.createJsonParser(inputStream);
}
private void configure(CharSequence charSequence) throws IOException, JsonParseException
{
// configure the json parser
JsonFactory f = new JsonFactory();
jp = f.createJsonParser(charSequence.toString());
}
private Object parse() throws IOException, JsonParseException, SIMPLTranslationException
{
debugContext = new StringBuilder();
// all JSON documents start with an opening brace.
if (jp.nextToken() != JsonToken.START_OBJECT)
{
println("JSON Translation ERROR: not a valid JSON object. It should start with {");
}
// move the first field in the document. typically it is the root element.
jp.nextToken();
Object root = null;
// find the classdescriptor for the root element.
ClassDescriptor<?> rootClassDescriptor = translationScope.getClassDescriptorByTag(jp
.getCurrentName());
root = rootClassDescriptor.getInstance();
// Logic to set all field descritpro scalars to defaults.
for(FieldDescriptor fd : rootClassDescriptor.allFieldDescriptors())
{
if(fd.isScalar() && (fd.isEnum() == false))
{
fd.setFieldToScalarDefault(root, translationContext);
}
}
// root.setupRoot();
deserializationPreHook(root, translationContext);
if (deserializationHookStrategy != null)
deserializationHookStrategy.deserializationPreHook(root, null);
// move to the first field of the root element.
jp.nextToken();
jp.nextToken();
// complete the object model from root and recursively of the fields it is composed of
createObjectModel(root, rootClassDescriptor);
return root;
}
/**
* Recursive method that moves forward in the CharSequence through JsonParser to create a
* corresponding object model
*
* @param root
* instance of the root element created by the calling method
* @param rootClassDescriptor
* instance of the classdescriptor of the root element created by the calling method
* @throws JsonParseException
* @throws IOException
* @throws SIMPLTranslationException
*/
private void createObjectModel(Object root, ClassDescriptor rootClassDescriptor)
throws JsonParseException, IOException, SIMPLTranslationException
{
debug(debugContext.toString()
+ "createObjectModel("
+ (root == null ? "<null>" : root.toString())
+ ", "
+ (rootClassDescriptor == null ? "<null>" : rootClassDescriptor.toString())
+ ")");
debugContext.append("----");
FieldDescriptor currentFieldDescriptor = null;
Object subRoot = null;
DeserializationProcedureState state = DeserializationProcedureState.INIT;
// iterate through each element of the current composite element.
while (jp.getCurrentToken() != JsonToken.END_OBJECT)
{
if (!handleSimplId(jp.getText(), root))
{
// currentFieldDescriptor = (currentFieldDescriptor != null)
// && (currentFieldDescriptor.getType() == IGNORED_ELEMENT) ? FieldDescriptor.IGNORED_ELEMENT_FIELD_DESCRIPTOR
// : (currentFieldDescriptor != null && currentFieldDescriptor.getType() == WRAPPER) ? currentFieldDescriptor
// .getWrappedFD()
// : rootClassDescriptor.getFieldDescriptorByTag(jp.getText(), translationScope, null);
FieldDescriptor oldCurrentFieldDescritpr = currentFieldDescriptor;
byte path = 0;
String fieldTag = null;
if (currentFieldDescriptor != null && currentFieldDescriptor.getType() == FieldType.IGNORED_ELEMENT)
{
path = 1;
currentFieldDescriptor = FieldDescriptor.IGNORED_ELEMENT_FIELD_DESCRIPTOR;
}
else
{
if (currentFieldDescriptor != null && currentFieldDescriptor.getType() == FieldType.WRAPPER)
{
path = 2;
currentFieldDescriptor = currentFieldDescriptor.getWrappedFD();
}
else
{
path = 3;
fieldTag = jp.getText();
currentFieldDescriptor = rootClassDescriptor.getFieldDescriptorByTag(fieldTag, translationScope, null);
}
}
FieldType fieldType = currentFieldDescriptor.getType();
String message = debugContext.toString() + "processing field " + currentFieldDescriptor.getName();
debug(message);
switch (fieldType)
{
case SCALAR:
jp.nextToken();
currentFieldDescriptor.setFieldToScalar(root, jp.getText(), translationContext);
break;
case COMPOSITE_ELEMENT:
jp.nextToken();
String tagName = jp.getCurrentName();
subRoot = getSubRoot(currentFieldDescriptor, tagName);
ClassDescriptor subRootClassDescriptor = currentFieldDescriptor
.getChildClassDescriptor(tagName);
// if (subRoot != null)
// subRoot.setupInParent(root, subRootClassDescriptor);
currentFieldDescriptor.setFieldToComposite(root, subRoot);
break;
case COLLECTION_ELEMENT:
jp.nextToken();
if (currentFieldDescriptor.isPolymorphic())
{
// ignore the wrapper tag
if (!currentFieldDescriptor.isWrapped())
jp.nextToken();
while (jp.getCurrentToken() != JsonToken.END_ARRAY)
{
jp.nextToken();
jp.nextToken();
subRoot = getSubRoot(currentFieldDescriptor, jp.getCurrentName());
Collection collection = (Collection) currentFieldDescriptor
.automaticLazyGetCollectionOrMap(root);
collection.add(subRoot);
jp.nextToken();
jp.nextToken();
}
}
else
{
while (jp.nextToken() != JsonToken.END_ARRAY)
{
subRoot = getSubRoot(currentFieldDescriptor, jp.getCurrentName());
Collection collection = (Collection) currentFieldDescriptor
.automaticLazyGetCollectionOrMap(root);
collection.add(subRoot);
}
}
break;
case MAP_ELEMENT:
jp.nextToken();
if (currentFieldDescriptor.isPolymorphic())
{
// ignore the wrapper tag
if (!currentFieldDescriptor.isWrapped())
jp.nextToken();
while (jp.getCurrentToken() != JsonToken.END_ARRAY)
{
jp.nextToken();
jp.nextToken();
subRoot = getSubRoot(currentFieldDescriptor, jp.getCurrentName());
if (subRoot instanceof IMappable)
{
final Object key = ((IMappable) subRoot).key();
Map map = (Map) currentFieldDescriptor.automaticLazyGetCollectionOrMap(root);
map.put(key, subRoot);
}
jp.nextToken();
jp.nextToken();
}
}
else
{
while (jp.nextToken() != JsonToken.END_ARRAY)
{
subRoot = getSubRoot(currentFieldDescriptor, jp.getCurrentName());
if (subRoot instanceof IMappable)
{
final Object key = ((IMappable) subRoot).key();
Map map = (Map) currentFieldDescriptor.automaticLazyGetCollectionOrMap(root);
map.put(key, subRoot);
}
}
}
break;
case COLLECTION_SCALAR:
jp.nextToken();
while (jp.nextToken() != JsonToken.END_ARRAY)
{
currentFieldDescriptor.addLeafNodeToCollection(root, jp.getText(), translationContext);
}
break;
case WRAPPER:
if (!currentFieldDescriptor.getWrappedFD().isPolymorphic())
jp.nextToken();
break;
}
state = nextDeserializationProcedureState(state, fieldType);
if (state == DeserializationProcedureState.ATTRIBUTES_DONE)
{
// when we know that definitely all attributes are done, we do the in-hook
deserializationInHook(subRoot, translationContext);
if (deserializationHookStrategy != null)
deserializationHookStrategy.deserializationInHook(subRoot, currentFieldDescriptor);
state = DeserializationProcedureState.ELEMENTS;
}
}
jp.nextToken();
}
state = DeserializationProcedureState.ELEMENTS_DONE;
deserializationPostHook(root, translationContext);
if (deserializationHookStrategy != null)
deserializationHookStrategy.deserializationPostHook(root,
currentFieldDescriptor == null || currentFieldDescriptor.getType() == FieldType.IGNORED_ELEMENT
? null : currentFieldDescriptor);
int debugContextLen = debugContext.length();
if (debugContextLen > 0)
debugContext.delete(debugContextLen - 4, debugContextLen);
}
/**
* Gets the sub root of the object model if its a composite object. Does graph handling Handles
* simpl.ref tag to assign an already created instance of the composite object instead of creating
* a new one
*
* @param currentFieldDescriptor
* @return
* @throws SIMPLTranslationException
* @throws JsonParseException
* @throws IOException
*/
private Object getSubRoot(FieldDescriptor currentFieldDescriptor, String tagName)
throws SIMPLTranslationException, JsonParseException, IOException
{
jp.nextToken(); // OBJECT_START
Object subRoot = null;
if (jp.getCurrentToken() == JsonToken.FIELD_NAME)
{
// check for simpl.ref if exists that we need an already created instance
if (jp.getText().equals(TranslationContext.JSON_SIMPL_REF))
{
jp.nextToken();
subRoot = translationContext.getFromMap(jp.getText());
jp.nextToken(); // OBJECT_END
}
else
{
ClassDescriptor<?> subRootClassDescriptor = currentFieldDescriptor
.getChildClassDescriptor(tagName);
subRoot = subRootClassDescriptor.getInstance();
// Logic to set all field descritpro scalars to defaults.
for(FieldDescriptor fd : subRootClassDescriptor.allFieldDescriptors())
{
if(fd.isScalar() && (fd.isEnum() == false))
{
fd.setFieldToScalarDefault(subRoot, translationContext);
}
}
deserializationPreHook(subRoot, translationContext);
if (deserializationHookStrategy != null)
deserializationHookStrategy.deserializationPreHook(subRoot, currentFieldDescriptor);
createObjectModel(subRoot, subRootClassDescriptor);
}
}
if (deserializationHookStrategy != null && subRoot != null)
{
Object newSubRoot= deserializationHookStrategy.changeObjectIfNecessary(subRoot, currentFieldDescriptor);
if (newSubRoot != null)
subRoot = newSubRoot;
}
return subRoot;
}
/**
* Function used for handling graph's simpl.id tag. If the tag is present the current ElementState
* object is marked as unmarshalled. Therefore, later simpl.ref can be used to extract this
* instance
*
* @param tagName
* @param root
* @return
* @throws JsonParseException
* @throws IOException
*/
private boolean handleSimplId(String tagName, Object root) throws JsonParseException, IOException
{
if (tagName.equals(TranslationContext.JSON_SIMPL_ID))
{
jp.nextToken();
translationContext.markAsUnmarshalled(jp.getText(), root);
return true;
}
return false;
}
}