package ecologylab.serialization.serializers.stringformats;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import ecologylab.serialization.ClassDescriptor;
import ecologylab.serialization.FieldDescriptor;
import ecologylab.serialization.FieldTypes;
import ecologylab.serialization.SIMPLTranslationException;
import ecologylab.serialization.SimplTypesScope;
import ecologylab.serialization.SimplTypesScope.GRAPH_SWITCH;
import ecologylab.serialization.TranslationContext;
import ecologylab.serialization.XMLTools;
import ecologylab.serialization.annotations.FieldUsage;
import ecologylab.serialization.formatenums.Format;
/***
* JSONSerializaton. Guides serialization of data in JSON. Contains code that is specific to
* creating a valid JSON of the provided object. Supports graph handling.
*
* @author nabeel
*
*/
public class JSONSerializer extends StringSerializer implements FieldTypes
{
private int numOfFields;
public JSONSerializer()
{
}
@Override
public void serialize(Object object, Appendable appendable, TranslationContext translationContext)
throws SIMPLTranslationException
{
translationContext.resolveGraph(object);
ClassDescriptor<? extends FieldDescriptor> rootObjectClassDescriptor = ClassDescriptor
.getClassDescriptor(object.getClass());
try
{
writeStart(appendable);
serialize(object, rootObjectClassDescriptor.pseudoFieldDescriptor(), appendable,
translationContext, true);
writeClose(appendable);
}
catch (IOException e)
{
throw new SIMPLTranslationException("IO Exception occurred", e);
}
}
/**
*
* @param object
* @param rootObjectFieldDescriptor
* @param appendable
* @param translationContext
* @param withTag
* @throws SIMPLTranslationException
* @throws IOException
*/
private void serialize(Object object, FieldDescriptor rootObjectFieldDescriptor,
Appendable appendable, TranslationContext translationContext, boolean withTag)
throws SIMPLTranslationException, IOException
{
if (alreadySerialized(object, translationContext))
{
writeSimplRef(object, rootObjectFieldDescriptor, withTag, appendable, translationContext);
return;
}
translationContext.mapObject(object);
serializationPreHook(object, translationContext);
writeObjectStart(rootObjectFieldDescriptor, appendable, withTag);
//numOfFields = 0;
ClassDescriptor<? extends FieldDescriptor> classDescriptor = getClassDescriptor(object);
serializeFields(object, appendable, translationContext, classDescriptor);
writeClose(appendable);
serializationPostHook(object, translationContext);
}
/**
*
* @param object
* @param appendable
* @param translationContext
* @param allFieldDescriptors
* @throws SIMPLTranslationException
* @throws IOException
*/
private void serializeFields(Object object, Appendable appendable,
TranslationContext translationContext,
ClassDescriptor<? extends FieldDescriptor> classDescriptor) throws SIMPLTranslationException,
IOException
{
ArrayList<? extends FieldDescriptor> allFieldDescriptors = classDescriptor.allFieldDescriptors();
if (SimplTypesScope.graphSwitch == GRAPH_SWITCH.ON)
{
if (translationContext.needsHashCode(object))
{
writeSimplIdAttribute(object, appendable, allFieldDescriptors.size() <= 0);
}
}
ArrayList<? extends FieldDescriptor> attributeFieldDescriptors = classDescriptor.attributeFieldDescriptors();
int numOfFields = serializeFieldsHelper(appendable, object, translationContext, attributeFieldDescriptors, 0);
ArrayList<? extends FieldDescriptor> elementFieldDescriptors = classDescriptor.elementFieldDescriptors();
serializeFieldsHelper(appendable, object, translationContext, elementFieldDescriptors,numOfFields);
}
private int serializeFieldsHelper(Appendable appendable, Object object,
TranslationContext translationContext,
ArrayList<? extends FieldDescriptor> fieldDescriptorList, int numOfFields) throws SIMPLTranslationException,
IOException
{
for (FieldDescriptor childFd : fieldDescriptorList)
{
if (isSerializable(childFd, object))
{
if (numOfFields++ > 0)
appendable.append(',');
switch (childFd.getType())
{
case SCALAR:
serializeScalar(object, childFd, appendable, translationContext);
break;
case COMPOSITE_ELEMENT:
serializeComposite(object, appendable, translationContext, childFd);
break;
case COLLECTION_SCALAR:
case MAP_SCALAR:
serializeScalarCollection(object, appendable, translationContext, childFd);
break;
case COLLECTION_ELEMENT:
case MAP_ELEMENT:
if (childFd.isPolymorphic())
serializePolymorphicCollection(object, appendable, translationContext, childFd);
else
serializeCompositeCollection(object, appendable, translationContext, childFd);
break;
}
}
}
return numOfFields;
}
/**
* check if the field is of default value or null. we don't have to serialize that field
*
* @param childFd
* @param object
* @return
* @throws SIMPLTranslationException
*/
private boolean isSerializable(FieldDescriptor childFd, Object object)
throws SIMPLTranslationException
{
switch (childFd.getType())
{
case SCALAR:
if (childFd.isDefaultValueFromContext(object))
return false;
break;
case COMPOSITE_ELEMENT:
case COLLECTION_ELEMENT:
case MAP_ELEMENT:
Object obj = childFd.getValue(object);
if (obj == null)
return false;
break;
case COLLECTION_SCALAR:
case MAP_SCALAR:
Object scalarCollectionObject = childFd.getValue(object);
Collection<?> scalarCollection = XMLTools.getCollection(scalarCollectionObject);
if (scalarCollection == null || scalarCollection.size() <= 0)
return false;
break;
}
return true;
}
/**
*
* @param object
* @param appendable
* @param translationContext
* @param childFd
* @throws SIMPLTranslationException
* @throws IOException
*/
private void serializeComposite(Object object, Appendable appendable,
TranslationContext translationContext, FieldDescriptor childFd)
throws SIMPLTranslationException, IOException
{
Object compositeObject = childFd.getValue(object);
FieldDescriptor compositeObjectFieldDescriptor = childFd.isPolymorphic() ? getClassDescriptor(
compositeObject).pseudoFieldDescriptor() : childFd;
serialize(compositeObject, compositeObjectFieldDescriptor, appendable, translationContext, true);
}
/**
*
* @param object
* @param appendable
* @param translationContext
* @param childFd
* @throws IOException
* @throws SIMPLTranslationException
*/
private void serializeCompositeCollection(Object object, Appendable appendable,
TranslationContext translationContext, FieldDescriptor childFd) throws IOException,
SIMPLTranslationException
{
Object collectionObject = childFd.getValue(object);
Collection<?> compositeCollection = XMLTools.getCollection(collectionObject);
if(compositeCollection != null)
{
int numberOfItems = 0;
writeWrap(childFd, appendable, false);
writeCollectionStart(childFd, appendable);
for (Object collectionComposite : compositeCollection)
{
FieldDescriptor collectionObjectFieldDescriptor = childFd.isPolymorphic() ? getClassDescriptor(
collectionComposite).pseudoFieldDescriptor()
: childFd;
serialize(collectionComposite, collectionObjectFieldDescriptor, appendable,
translationContext, false);
if (++numberOfItems < compositeCollection.size())
appendable.append(',');
}
writeCollectionEnd(appendable);
writeWrap(childFd, appendable, true);
}
}
/**
*
* @param object
* @param appendable
* @param translationContext
* @param childFd
* @throws IOException
* @throws SIMPLTranslationException
*/
private void serializePolymorphicCollection(Object object, Appendable appendable,
TranslationContext translationContext, FieldDescriptor childFd) throws IOException,
SIMPLTranslationException
{
Object collectionObject = childFd.getValue(object);
Collection<?> compositeCollection = XMLTools.getCollection(collectionObject);
int numberOfItems = 0;
if(compositeCollection != null)
{
writePolymorphicCollectionStart(childFd, appendable);
for (Object collectionComposite : compositeCollection)
{
FieldDescriptor collectionObjectFieldDescriptor = childFd.isPolymorphic() ? getClassDescriptor(
collectionComposite).pseudoFieldDescriptor()
: childFd;
writeStart(appendable);
serialize(collectionComposite, collectionObjectFieldDescriptor, appendable,
translationContext, true);
writeClose(appendable);
if (++numberOfItems < compositeCollection.size())
appendable.append(',');
}
writeCollectionEnd(appendable);
}
}
/**
*
* @param object
* @param appendable
* @param translationContext
* @param childFd
* @throws IOException
* @throws SIMPLTranslationException
*/
private void serializeScalarCollection(Object object, Appendable appendable,
TranslationContext translationContext, FieldDescriptor childFd) throws IOException,
SIMPLTranslationException
{
Object scalarCollectionObject = childFd.getValue(object);
Collection<?> scalarCollection = XMLTools.getCollection(scalarCollectionObject);
int numberOfItems = 0;
if(scalarCollection != null)
{
writeWrap(childFd, appendable, false);
writeCollectionStart(childFd, appendable);
for (Object collectionObject : scalarCollection)
{
writeCollectionScalar(collectionObject, childFd, appendable, translationContext);
if (++numberOfItems < scalarCollection.size())
appendable.append(',');
}
writeCollectionEnd(appendable);
writeWrap(childFd, appendable, true);
}
}
/**
*
* @param object
* @param fd
* @param appendable
* @param translationContext
* @throws IOException
* @throws SIMPLTranslationException
*/
private void serializeScalar(Object object, FieldDescriptor fd, Appendable appendable,
TranslationContext translationContext) throws IOException, SIMPLTranslationException
{
appendable.append('"');
appendable.append(fd.getTagName());
appendable.append('"');
appendable.append(':');
appendable.append('"');
fd.appendValue(appendable, object, translationContext, Format.JSON);
appendable.append('"');
}
/**
*
* @param appendable
* @throws IOException
*/
private void writeCollectionEnd(Appendable appendable) throws IOException
{
appendable.append(']');
}
private void writeCollectionStart(FieldDescriptor fd, Appendable appendable) throws IOException
{
appendable.append('"').append(fd.elementStart()).append('"');
appendable.append(':');
appendable.append('[');
}
/**
*
* @param fd
* @param appendable
* @throws IOException
*/
private void writePolymorphicCollectionStart(FieldDescriptor fd, Appendable appendable)
throws IOException
{
appendable.append('"').append(fd.getTagName()).append('"');
appendable.append(':');
appendable.append('[');
}
/**
*
* @param fd
* @param appendable
* @param close
* @throws IOException
*/
private void writeWrap(FieldDescriptor fd, Appendable appendable, boolean close)
throws IOException
{
if (fd.isWrapped())
{
if (!close)
{
appendable.append('"');
appendable.append(fd.getTagName());
appendable.append('"').append(':');
appendable.append('{');
}
else
{
appendable.append('}');
}
}
}
/**
*
* @param object
* @param fd
* @param appendable
* @param translationContext
* @throws IOException
* @throws SIMPLTranslationException
*/
private void writeCollectionScalar(Object object, FieldDescriptor fd, Appendable appendable,
TranslationContext translationContext) throws IOException, SIMPLTranslationException
{
appendable.append('"');
fd.appendCollectionScalarValue(appendable, object, translationContext, Format.JSON);
appendable.append('"');
}
/**
*
* @param fd
* @param appendable
* @param withTag
* @throws IOException
*/
private void writeObjectStart(FieldDescriptor fd, Appendable appendable, boolean withTag)
throws IOException
{
if (withTag)
{
appendable.append('"').append(fd.elementStart()).append('"');
appendable.append(':');
}
appendable.append('{');
}
/**
*
* @param object
* @param rootObjectFieldDescriptor
* @param appendable
* @throws IOException
*/
private void writeSimplRef(Object object, FieldDescriptor fd, boolean withTag,
Appendable appendable, TranslationContext translationContext) throws IOException
{
writeObjectStart(fd, appendable, withTag);
writeSimplRefAttribute(object, appendable, translationContext);
writeClose(appendable);
}
private void writeSimplRefAttribute(Object object, Appendable appendable, TranslationContext translationContext) throws IOException
{
appendable.append('"');
appendable.append(TranslationContext.JSON_SIMPL_REF);
appendable.append('"');
appendable.append(':');
appendable.append('"');
appendable.append(translationContext.getSimplId(object));
appendable.append('"');
}
private void writeSimplIdAttribute(Object object, Appendable appendable, boolean last)
throws IOException
{
appendable.append('"');
appendable.append(TranslationContext.JSON_SIMPL_ID);
appendable.append('"');
appendable.append(':');
appendable.append('"');
appendable.append(((Integer) object.hashCode()).toString());
appendable.append('"');
if (!last)
{
appendable.append(',');
}
}
/**
*
* @param appendable
* @throws IOException
*/
private void writeStart(Appendable appendable) throws IOException
{
appendable.append('{');
}
/**
*
* @param appendable
* @throws IOException
*/
private void writeClose(Appendable appendable) throws IOException
{
appendable.append('}');
}
}