/**
* Copyright 2007 Charlie Hubbard and Brandon Goodin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package flexjson;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import flexjson.transformer.NullTransformer;
import flexjson.transformer.Transformer;
import flexjson.transformer.TransformerWrapper;
import flexjson.transformer.TypeTransformerMap;
public class JSONContext
{
private static ThreadLocal<JSONContext> context= new ThreadLocal<JSONContext>()
{
protected JSONContext initialValue()
{
return new JSONContext();
}
};
private String rootName;
private OutputHandler out;
private boolean prettyPrint= false;
private Stack<TypeContext> typeContextStack= new Stack<TypeContext>();
private int indent= 0;
private TypeTransformerMap typeTransformerMap;
private Map<Path, Transformer> pathTransformerMap;
private List<PathExpression> pathExpressions;
private SerializationType serializationType= SerializationType.SHALLOW;
private ChainedSet visits= new ChainedSet(Collections.EMPTY_SET);
private Stack<Object> objectStack= new Stack<Object>();
private Path path= new Path();
public int ids= 0;
public Map<Integer, Integer> references= new HashMap<Integer, Integer>();
private boolean serializingWithUniqueIds;
public JSONContext()
{
}
// CONFIGURE SERIALIZATION
public void serializationType(SerializationType serializationType)
{
this.serializationType= serializationType;
}
// CONFIGURE TRANSFORMERS
/**
* Run a transformer on the provided object
*
* @param object
* @return
*/
public void transform(Object object)
{
Transformer transformer= getPathTransformer(object);
if (transformer == null)
{
transformer= getTypeTransformer(object);
}
if (transformer == null)
transformer= new TransformerWrapper(new NullTransformer());
transformer.transform(object);
}
/**
* Retrieves a transformer for the provided object
*
* @param object
* @return
*/
public Transformer getTransformer(Object object)
{
Transformer transformer= getPathTransformer(object);
if (transformer == null)
{
transformer= getTypeTransformer(object);
}
return transformer;
}
private Transformer getPathTransformer(Object object)
{
if (null == object)
return getTypeTransformer(object);
return pathTransformerMap.get(path);
}
private Transformer getTypeTransformer(Object object)
{
return typeTransformerMap.getTransformer(object);
}
/**
* used to pass in configured transformers from the JsonSerializer
*
* @param typeTransformerMap
*/
public void setTypeTransformers(TypeTransformerMap typeTransformerMap)
{
this.typeTransformerMap= typeTransformerMap;
}
/**
* used to pass in configured transformers from the JsonSerializer
*
* @param pathTransformerMap
*/
public void setPathTransformers(Map<Path, Transformer> pathTransformerMap)
{
this.pathTransformerMap= pathTransformerMap;
}
// OUTPUT
/**
* configures the context to output JSON with new lines and indentations
*
* @param prettyPrint
*/
public void setPrettyPrint(boolean prettyPrint)
{
this.prettyPrint= prettyPrint;
}
public void pushTypeContext(TypeContext contextEnum)
{
typeContextStack.push(contextEnum);
}
public void popTypeContext()
{
typeContextStack.pop();
}
public TypeContext peekTypeContext()
{
if (!typeContextStack.isEmpty())
{
return typeContextStack.peek();
}
else
{
return null;
}
}
/**
* Set the output handler.
*
* @param out
*/
public void setOut(OutputHandler out)
{
this.out= out;
}
/**
* getTransformer output handler
*
* @return
*/
public OutputHandler getOut()
{
return out;
}
/**
* write a simple non-quoted value to output
*
* @param value
*/
public void write(String value)
{
TypeContext currentTypeContext= peekTypeContext();
if (currentTypeContext != null && currentTypeContext.getBasicType() == BasicType.ARRAY)
{
writeIndent();
}
out.write(value);
}
public TypeContext writeOpenObject()
{
if (prettyPrint)
{
TypeContext currentTypeContext= peekTypeContext();
if (currentTypeContext != null && currentTypeContext.getBasicType() == BasicType.ARRAY)
{
writeIndent();
}
}
TypeContext typeContext= new TypeContext(BasicType.OBJECT);
pushTypeContext(typeContext);
out.write("{");
if (prettyPrint)
{
indent+= 4;
out.write("\n");
}
return typeContext;
}
public void writeCloseObject()
{
if (prettyPrint)
{
out.write("\n");
indent-= 4;
writeIndent();
}
out.write("}");
popTypeContext();
}
public void writeName(String name)
{
if (prettyPrint)
writeIndent();
if (name != null)
writeQuoted(name);
else
write("null");
out.write(":");
if (prettyPrint)
out.write(" ");
}
public void writeComma()
{
out.write(",");
if (prettyPrint)
{
out.write("\n");
}
}
public TypeContext writeOpenArray()
{
if (prettyPrint)
{
TypeContext currentTypeContext= peekTypeContext();
if (currentTypeContext != null && currentTypeContext.getBasicType() == BasicType.ARRAY)
{
writeIndent();
}
}
TypeContext typeContext= new TypeContext(BasicType.ARRAY);
pushTypeContext(typeContext);
out.write("[");
if (prettyPrint)
{
indent+= 4;
out.write("\n");
}
return typeContext;
}
public void writeCloseArray()
{
if (prettyPrint)
{
out.write("\n");
indent-= 4;
writeIndent();
}
out.write("]");
popTypeContext();
}
public void writeIndent()
{
for (int i= 0; i < indent; i++)
{
out.write(" ");
}
}
/**
* write a quoted and escaped value to the output
*
* @param value
*/
public void writeQuoted(String value)
{
if (prettyPrint)
{
TypeContext currentTypeContext= peekTypeContext();
if (currentTypeContext != null && currentTypeContext.getBasicType() == BasicType.ARRAY)
{
writeIndent();
}
}
out.write("\"");
int last= 0;
int len= value.length();
for (int i= 0; i < len; i++)
{
char c= value.charAt(i);
if (c == '"')
{
last= out.write(value, last, i, "\\\"");
}
else if (c == '\\')
{
last= out.write(value, last, i, "\\\\");
}
else if (c == '\b')
{
last= out.write(value, last, i, "\\b");
}
else if (c == '\f')
{
last= out.write(value, last, i, "\\f");
}
else if (c == '\n')
{
last= out.write(value, last, i, "\\n");
}
else if (c == '\r')
{
last= out.write(value, last, i, "\\r");
}
else if (c == '\t')
{
last= out.write(value, last, i, "\\t");
}
else if (true || Character.isISOControl(c))
{
last= out.write(value, last, i) + 1;
unicode(c);
}
}
if (last < value.length())
{
out.write(value, last, value.length());
}
out.write("\"");
}
private void unicode(char c)
{
out.write(c + "");
// out.write("\\u");
// int n= c;
// for (int i= 0; i < 4; ++i)
// {
// int digit= (n & 0xf000) >> 12;
// out.write(String.valueOf(JSONSerializer.HEX[digit]));
// n<<= 4;
// }
}
// MANAGE CONTEXT
/**
* static method to getTransformer the context for this thread
*
* @return
*/
public static JSONContext get()
{
return context.get();
}
/**
* static moethod to clean up thread when serialization is complete
*/
public static void cleanup()
{
context.remove();
}
// INCLUDE/EXCLUDE METHODS
public ChainedSet getVisits()
{
return visits;
}
public void setVisits(ChainedSet visits)
{
this.visits= visits;
}
public Stack<Object> getObjectStack()
{
return objectStack;
}
public String getRootName()
{
return rootName;
}
public void setRootName(String rootName)
{
this.rootName= rootName;
}
public Path getPath()
{
return this.path;
}
public void setPathExpressions(List<PathExpression> pathExpressions)
{
this.pathExpressions= pathExpressions;
}
public boolean isIncluded(BeanProperty prop)
{
PathExpression expression= matches(pathExpressions);
if (expression != null)
{
return expression.isIncluded();
}
Boolean annotation= prop.isAnnotated();
if (annotation != null)
{
return annotation;
}
if (serializationType == SerializationType.SHALLOW)
{
Class propType= prop.getPropertyType();
return !(propType.isArray() || Iterable.class.isAssignableFrom(propType));
}
else
{
return true;
}
}
public boolean isIncluded(String key, Object value)
{
PathExpression expression= matches(pathExpressions);
if (expression != null)
{
return expression.isIncluded();
}
String rootName= context.get().getRootName();
/*
* We have a double check here because of the way lists are handled in a shallow. Normally
* lists are ignored. but, in the case when a rootName is added the object being serialized
* get wrapped with a Map and may be a List/Iterable. We don't want the List to get ignored.
* So, we check if a rootName has been specified and then make sure we are past the root
* element serialization before we begin to ingore List and Iterable.
*/
if (value != null && ((serializationType == SerializationType.SHALLOW && (rootName != null && path.length() > 1)) || (serializationType == SerializationType.SHALLOW && (rootName == null))))
{
Class type= value.getClass();
return !(type.isArray() || Iterable.class.isAssignableFrom(type));
}
else
{
return true;
}
}
public boolean isIncluded(Field field)
{
PathExpression expression= matches(pathExpressions);
if (expression != null)
{
return expression.isIncluded();
}
if (field.isAnnotationPresent(JSON.class))
{
return field.getAnnotation(JSON.class).include();
}
if (serializationType == SerializationType.SHALLOW)
{
Class type= field.getType();
return !(type.isArray() || Iterable.class.isAssignableFrom(type));
}
else
{
return true;
}
}
public boolean isValidField(Field field)
{
return !Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers()) && !Modifier.isTransient(field.getModifiers());
}
protected PathExpression matches(List<PathExpression> expressions)
{
for (PathExpression expr : expressions)
{
if (expr.matches(path))
{
return expr;
}
}
return null;
}
public Map<Integer, Integer> getReferences()
{
return references;
}
public boolean isSerializingWithUniqueIds()
{
return serializingWithUniqueIds;
}
public void setSerializingWithUniqueIds(boolean serializingWithUniqueIds)
{
this.serializingWithUniqueIds= serializingWithUniqueIds;
}
}