Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.cedarsoftware.util.io.GroovyJsonWriter.groovy Maven / Gradle / Ivy
package com.cedarsoftware.util.io
import groovy.transform.CompileStatic
import java.lang.reflect.Array
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.sql.Timestamp
import java.util.Map.Entry
import java.util.concurrent.ConcurrentHashMap
/**
* Output a Groovy object graph in JSON format. This code handles cyclic
* references and can serialize any Object graph without requiring a class
* to be 'Serializeable' or have any specific methods on it.
*
* Call the static method: {@code JsonWriter.toJson(employee)}. This will
* convert the passed in 'employee' instance into a JSON String.
* Using streams:
* JsonWriter writer = new JsonWriter(stream)
* writer.write(employee)
* writer.close()
* This will write the 'employee' object to the passed in OutputStream.
*
* That's it. This can be used as a debugging tool. Output an object
* graph using the above code. You can copy that JSON output into this site
* which formats it with a lot of whitespace to make it human readable:
* http://jsonformatter.curiousconcept.com
*
*
This will output any object graph deeply (or null). Object references are
* properly handled. For example, if you had A->B, B->C, and C->A, then
* A will be serialized with a B object in it, B will be serialized with a C
* object in it, and then C will be serialized with a reference to A (ref), not a
* redefinition of A.
*
*
* @author John DeRegnaucourt ([email protected] )
*
* Copyright (c) Cedar Software LLC
*
* 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.
*/
@CompileStatic
class GroovyJsonWriter implements Closeable, Flushable
{
static final String DATE_FORMAT = "DATE_FORMAT" // Set the date format to use within the JSON output
static final String ISO_DATE_FORMAT = "yyyy-MM-dd" // Constant for use as DATE_FORMAT value
static final String ISO_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss" // Constant for use as DATE_FORMAT value
static final String TYPE_NAME_MAP = "TYPE_NAME_MAP" // If set, this map will be used when writing @type values - allows short-hand abbreviations type names
static final String SHORT_META_KEYS = "SHORT_META_KEYS" // If set, then @type -> @t, @keys -> @k, @items -> @i
static final String TYPE = "TYPE" // Force @type always
static final String PRETTY_PRINT = "PRETTY_PRINT" // Force nicely formatted JSON output
static final String FIELD_SPECIFIERS = "FIELD_SPECIFIERS" // Set value to a Map> which will be used to control which fields on a class are output
static final String ENUM_PUBLIC_ONLY = "ENUM_PUBLIC_ONLY" // If set, indicates that private variables of ENUMs are not to be serialized
static final String WRITE_LONGS_AS_STRINGS = "WLAS" // If set, longs are written in quotes (Javascript safe)
private static final Map writers = [:]
private static final Set notCustom = [] as Set
private static final Object[] byteStrings = new Object[256]
private static final String newLine = System.getProperty("line.separator")
private static final Long ZERO = 0L
private static final Map writerCache = new ConcurrentHashMap<>()
private static final NullClass nullWriter = new NullClass()
private final Map objVisited = new IdentityHashMap<>()
private final Map objsReferenced = new IdentityHashMap<>()
private final Writer out
private Map typeNameMap = null;
private final Map customArgs = new HashMap<>()
private boolean shortMetaKeys = false;
long identity = 1
private int depth = 0
// _args is using ThreadLocal so that static inner classes can have access to them
static final ThreadLocal> _args = new ThreadLocal>() {
Map initialValue()
{
return [:]
}
}
static
{
for (short i = -128; i <= 127; i++)
{
char[] chars = Integer.toString(i).toCharArray()
byteStrings[i + 128] = chars
}
writers[String.class] = new Writers.JsonStringWriter()
writers[Date.class] = new Writers.DateWriter()
writers[BigInteger.class] = new Writers.BigIntegerWriter()
writers[BigDecimal.class] = new Writers.BigDecimalWriter()
writers[java.sql.Date.class] = new Writers.DateWriter()
writers[Timestamp.class] = new Writers.TimestampWriter()
writers[Calendar.class] = new Writers.CalendarWriter()
writers[TimeZone.class] = new Writers.TimeZoneWriter()
writers[Locale.class] = new Writers.LocaleWriter()
writers[Class.class] = new Writers.ClassWriter()
writers[StringBuilder.class] = new Writers.StringBuilderWriter()
writers[StringBuffer.class] = new Writers.StringBufferWriter()
}
/**
* @see GroovyJsonWriter#objectToJson(Object, java.util.Map)
*/
static String objectToJson(Object item)
{
return objectToJson(item, new HashMap())
}
/**
* @return The arguments used to configure the JsonWriter. These are thread local.
*/
protected static Map getArgs()
{
return _args.get()
}
/**
* Provide access to subclasses.
*/
protected Map getObjectsReferenced()
{
return objsReferenced
}
/**
* Provide access to subclasses.
*/
protected Map getObjectsVisited()
{
return objVisited
}
protected String getSubstituteTypeNameIfExists(String typeName)
{
if (typeNameMap == null)
{
return null
}
return typeNameMap[typeName]
}
protected String getSubstituteTypeName(String typeName)
{
if (typeNameMap == null)
{
return typeName
}
String shortName = typeNameMap[typeName]
return shortName == null ? typeName : shortName
}
/**
* Convert a Groovy Object to a JSON String.
*
* @param item Object to convert to a JSON String.
* @param optionalArgs (optional) Map of extra arguments indicating how dates are formatted,
* what fields are written out (optional). For Date parameters, use the public static
* DATE_TIME key, and then use the ISO_DATE or ISO_DATE_TIME indicators. Or you can specify
* your own custom SimpleDateFormat String, or you can associate a SimpleDateFormat object,
* in which case it will be used. This setting is for both java.util.Date and java.sql.Date.
* If the DATE_FORMAT key is not used, then dates will be formatted as longs. This long can
* be turned back into a date by using 'new Date(longValue)'.
* @return String containing JSON representation of passed
* in object.
*/
static String objectToJson(Object item, Map optionalArgs)
{
ByteArrayOutputStream stream = new ByteArrayOutputStream()
GroovyJsonWriter writer = new GroovyJsonWriter(stream, optionalArgs)
writer.write(item)
writer.close()
return new String(stream.toByteArray(), "UTF-8")
}
/**
* Format the passed in JSON string in a nice, human readable format.
* @param json String input JSON
* @return String containing equivalent JSON, formatted nicely for human readability.
*/
static String formatJson(String json)
{
Map map = GroovyJsonReader.jsonToMaps(json)
return objectToJson(map, [(PRETTY_PRINT):true] as Map)
}
/**
* @see GroovyJsonWriter#GroovyJsonWriter(java.io.OutputStream, java.util.Map)
*/
GroovyJsonWriter(OutputStream out)
{
this(out, [:])
}
/**
* @param out OutputStream to which the JSON output will be written.
* @param optionalArgs (optional) Map of extra arguments indicating how dates are formatted,
* what fields are written out (optional). For Date parameters, use the public static
* DATE_TIME key, and then use the ISO_DATE or ISO_DATE_TIME indicators. Or you can specify
* your own custom SimpleDateFormat String, or you can associate a SimpleDateFormat object,
* in which case it will be used. This setting is for both java.util.Date and java.sql.Date.
* If the DATE_FORMAT key is not used, then dates will be formatted as longs. This long can
* be turned back into a date by using 'new Date(longValue)'.
*/
GroovyJsonWriter(OutputStream out, Map optionalArgs)
{
customArgs[JsonTypeWriterEx.JSON_WRITER] = this
Map args = _args.get()
args.clear()
args.putAll(optionalArgs)
typeNameMap = (Map) args[TYPE_NAME_MAP]
shortMetaKeys = Boolean.TRUE.equals(args[SHORT_META_KEYS])
if (optionalArgs.containsKey(FIELD_SPECIFIERS))
{ // Convert String field names to Groovy Field instances (makes it easier for user to set this up)
Map> specifiers = (Map>) args[FIELD_SPECIFIERS]
Map> copy = [:]
for (Entry> entry : specifiers.entrySet())
{
Class clazz = entry.key
List fields = entry.value
List newList = new ArrayList(fields.size())
Map classFields = MetaUtils.getDeepDeclaredFields(clazz)
for (String field : fields)
{
Field f = classFields[field]
if (f == null)
{
throw new JsonIoException("Unable to locate field: " + field + " on class: " + clazz.getName() + ". Make sure the fields in the FIELD_SPECIFIERS map existing on the associated class.")
}
newList.add(f)
}
copy[clazz] = newList
}
args[FIELD_SPECIFIERS] = copy
}
else
{ // Ensure that at least an empty Map is in the FIELD_SPECIFIERS entry
args[FIELD_SPECIFIERS] = [:]
}
try
{
this.out = new BufferedWriter(new OutputStreamWriter(out, 'UTF-8'))
}
catch (UnsupportedEncodingException e)
{
throw new JsonIoException('Unsupported encoding. Get a JVM that supports UTF-8', e)
}
}
static boolean isPublicEnumsOnly()
{
return isTrue(_args.get()[ENUM_PUBLIC_ONLY])
}
static boolean isPrettyPrint()
{
return isTrue(_args.get()[PRETTY_PRINT])
}
static boolean getWriteLongsAsStrings()
{
return isTrue(_args.get()[WRITE_LONGS_AS_STRINGS])
}
private static boolean isTrue(Object setting)
{
if (setting instanceof Boolean)
{
return Boolean.TRUE.equals(setting)
}
else if (setting instanceof String)
{
return 'true'.equalsIgnoreCase((String) setting)
}
else if (setting instanceof Number)
{
return ((Number)setting).intValue() != 0;
}
return false
}
protected void tabIn()
{
tab(out, 1)
}
protected void newLine()
{
tab(out, 0)
}
protected void tabOut()
{
tab(out, -1)
}
private void tab(Writer output, int delta)
{
if (!isPrettyPrint())
{
return
}
output.write(newLine)
depth += delta;
for (int i=0; i < depth; i++)
{
output.write(" ")
}
}
boolean writeIfMatching(Object o, boolean showType, Writer output)
{
Class c = o.getClass()
if (notCustom.contains(c))
{
return false
}
return writeCustom(c, o, showType, output)
}
boolean writeArrayElementIfMatching(Class arrayComponentClass, Object o, boolean showType, Writer output)
{
if (!o.getClass().isAssignableFrom(arrayComponentClass) || notCustom.contains(o.getClass()))
{
return false
}
return writeCustom(arrayComponentClass, o, showType, output)
}
protected boolean writeCustom(Class arrayComponentClass, Object o, boolean showType, Writer output)
{
JsonTypeWriterBase closestWriter = getCustomWriter(arrayComponentClass)
if (closestWriter == null)
{
return false
}
if (writeOptionalReference(o))
{
return true
}
boolean referenced = objsReferenced.containsKey(o)
if (closestWriter instanceof JsonTypeWriter)
{
JsonTypeWriter writer = (JsonTypeWriter) closestWriter;
if (writer.hasPrimitiveForm())
{
if ((!referenced && !showType) || closestWriter instanceof Writers.JsonStringWriter)
{
writer.writePrimitiveForm(o, output)
return true
}
}
}
output.write('{')
tabIn()
if (referenced)
{
writeId(getId(o))
if (showType)
{
output.write(',')
newLine()
}
}
if (showType)
{
writeType(o, output)
}
if (referenced || showType)
{
output.write(',')
newLine()
}
if (closestWriter instanceof JsonTypeWriterEx)
{
((JsonTypeWriterEx)closestWriter).write(o, showType || referenced, output, customArgs);
}
else
{
((JsonTypeWriter)closestWriter).write(o, showType || referenced, output);
}
tabOut()
output.write('}')
return true
}
static class NullClass implements JsonTypeWriterBase { }
private static JsonTypeWriterBase getCustomWriter(Class c)
{
JsonTypeWriterBase writer = writerCache[c]
if (writer == null)
{
synchronized (writerCache)
{
writer = writerCache[c]
if (writer == null)
{
writer = getForceCustomWriter(c)
writerCache[c] = writer
}
}
}
return writer.is(nullWriter) ? null : writer
}
private static JsonTypeWriterBase getForceCustomWriter(Class c)
{
JsonTypeWriterBase closestWriter = nullWriter
int minDistance = Integer.MAX_VALUE
for (Entry entry : writers.entrySet())
{
Class clz = entry.key
if (clz == c)
{
return entry.value
}
int distance = MetaUtils.getDistance(clz, c)
if (distance < minDistance)
{
minDistance = distance
closestWriter = entry.value
}
}
return closestWriter
}
static void addWriter(Class c, JsonTypeWriterBase writer)
{
for (Entry entry : writers.entrySet())
{
Class clz = entry.key
if (clz.is(c))
{
entry.value = writer // Replace writer
return
}
}
writers[c] = writer
}
static void addNotCustomWriter(Class c)
{
writerCache[c] = nullWriter
}
void write(Object obj)
{
traceReferences(obj)
objVisited.clear()
writeImpl(obj, true)
flush()
objVisited.clear()
objsReferenced.clear()
_args.get().clear()
_args.remove()
}
/**
* Walk object graph and visit each instance, following each field, each Collection, Map and so on.
* Tracks visited count to handle cycles and to determine if an item is referenced elsewhere. If an
* object is never referenced more than once, no @id field needs to be emitted for it.
* @param root Object to be deeply traced. The objVisited and objsReferenced Maps will be written to
* during the trace.
*/
protected void traceReferences(Object root)
{
if (root == null)
{
return
}
Deque stack = new ArrayDeque<>()
stack.addFirst(root)
final Map visited = objVisited
final Map referenced = objsReferenced
while (!stack.isEmpty())
{
Object obj = stack.removeFirst()
if (!MetaUtils.isLogicalPrimitive(obj.getClass()))
{
Long id = visited[obj]
if (id != null)
{ // Only write an object once.
if (id == ZERO)
{ // 2nd time this object has been seen, so give it a unique ID and mark it referenced
id = identity++
visited[obj] = id
referenced[obj] = id
}
continue
}
else
{ // Initially, mark an object with 0 as the ID, in case it is never referenced,
// we don't waste the memory to store a Long instance that is never used.
visited[obj] = ZERO
}
}
final Class clazz = obj.getClass()
if (clazz.isArray())
{
Class compType = clazz.componentType
if (!MetaUtils.isLogicalPrimitive(compType))
{ // Speed up: do not traceReferences of primitives, they cannot reference anything
final int len = Array.getLength(obj)
for (int i = 0; i < len; i++)
{
Object o = Array.get(obj, i)
if (o != null)
{ // Slight perf gain (null is legal)
stack.addFirst(o)
}
}
}
}
else if (Map.class.isAssignableFrom(clazz))
{ // Speed up - logically walk maps, as opposed to following their internal structure.
Map map = (Map) obj
for (Object item : map.entrySet())
{
Entry entry = (Entry) item;
if (entry.value != null)
{
stack.addFirst(entry.value)
}
if (entry.key != null)
{
stack.addFirst(entry.key)
}
}
}
else if (Collection.class.isAssignableFrom(clazz))
{
for (Object item : (Collection)obj)
{
if (item != null)
{
stack.addFirst(item)
}
}
}
else
{
// Only trace fields if no custom writer is present
if (getCustomWriter(obj.getClass()) == null)
{
traceFields(stack, obj)
}
}
}
}
/**
* Reach-ability trace to visit all objects within the graph to be written.
* This API will handle any object, using either reflection APIs or by
* consulting a specified FIELD_SPECIFIERS map if provided.
*/
protected void traceFields(Deque stack, Object obj)
{
Map> fieldSpecifiers = (Map) _args.get()[FIELD_SPECIFIERS]
// If caller has special Field specifier for a given class
// then use it, otherwise use reflection.
Collection fields = getFieldsUsingSpecifier(obj.getClass(), fieldSpecifiers)
if (fields == null)
{ // Trace fields using reflection
fields = MetaUtils.getDeepDeclaredFields(obj.getClass()).values()
}
for (Field field : fields)
{
try
{
final Object o = field.get(obj)
if (o != null && !MetaUtils.isLogicalPrimitive(o.getClass()))
{
stack.addFirst(o)
}
}
catch (Exception ignored)
{ }
}
}
private static List getFieldsUsingSpecifier(Class classBeingWritten, Map> fieldSpecifiers)
{
Iterator>> i = fieldSpecifiers.entrySet().iterator()
int minDistance = Integer.MAX_VALUE
List fields = null
while (i.hasNext())
{
Entry> entry = i.next()
Class c = entry.key
if (c == classBeingWritten)
{
return entry.value
}
int distance = MetaUtils.getDistance(c, classBeingWritten)
if (distance < minDistance)
{
minDistance = distance
fields = entry.value
}
}
return fields
}
private boolean writeOptionalReference(Object obj)
{
if (obj != null && MetaUtils.isLogicalPrimitive(obj.getClass()))
{
return false
}
final Writer output = this.out
if (objVisited.containsKey(obj))
{ // Only write (define) an object once in the JSON stream, otherwise emit a @ref
String id = getId(obj)
if (id == null)
{ // Test for null because of Weak/Soft references being gc'd during serialization.
return false
}
output.write(shortMetaKeys ? '{"@r":' : '{"@ref":')
output.write(id)
output.write('}')
return true
}
// Mark the object as visited by putting it in the Map (this map is re-used / clear()'d after walk()).
objVisited[obj] = (Long)null
return false
}
void writeImpl(Object obj, boolean showType)
{
if (obj == null)
{
out.write('null')
return
}
if (obj.getClass().isArray())
{
writeArray(obj, showType)
}
else if (obj instanceof Collection)
{
writeCollection((Collection) obj, showType)
}
else if (obj instanceof JsonObject)
{ // symmetric support for writing Map of Maps representation back as equivalent JSON format.
JsonObject jObj = (JsonObject) obj
if (jObj.isArray())
{
writeJsonObjectArray(jObj, showType)
}
else if (jObj.isCollection())
{
writeJsonObjectCollection(jObj, showType)
}
else if (jObj.isMap())
{
if (!writeJsonObjectMapWithStringKeys(jObj, showType))
{
writeJsonObjectMap(jObj, showType)
}
}
else
{
writeJsonObjectObject(jObj, showType)
}
}
else if (obj instanceof Map)
{
if (!writeMapWithStringKeys((Map) obj, showType))
{
writeMap((Map) obj, showType)
}
}
else
{
writeObject(obj, showType)
}
}
private void writeId(final String id)
{
out.write(shortMetaKeys ? '"@i":' : '"@id":')
out.write(id == null ? "0" : id)
}
private void writeType(Object obj, Writer output)
{
output.write(shortMetaKeys ? '"@t":"' : '"@type":"')
final Class c = obj.getClass()
String typeName = c.getName()
String shortName = getSubstituteTypeNameIfExists(typeName)
if (shortName != null)
{
output.write(shortName)
output.write('"')
return
}
switch (c.getName())
{
case "java.lang.Boolean":
output.write("boolean")
break
case "java.lang.Byte":
output.write("byte")
break
case "java.lang.Character":
output.write("char")
break
case "java.lang.Class":
output.write("class")
break
case "java.lang.Double":
output.write("double")
break
case "java.lang.Float":
output.write("float")
break
case "java.lang.Integer":
output.write("int")
break
case "java.lang.Long":
output.write("long")
break
case "java.lang.Short":
output.write("short")
break
case "java.lang.String":
output.write("string")
break
case "java.util.Date":
output.write("date")
break
default:
output.write(c.getName())
break
}
output.write('"')
}
private void writePrimitive(final Object obj, boolean showType) throws IOException
{
if (obj instanceof Character)
{
writeJsonUtf8String(String.valueOf(obj), out)
}
else
{
if (obj instanceof Long && getWriteLongsAsStrings())
{
if (showType)
{
out.write(shortMetaKeys ? '{"@t":"' : '{"@type":"')
out.write(getSubstituteTypeName("long"))
out.write('","value":"')
out.write(obj.toString())
out.write('"}')
}
else
{
out.write('"')
out.write(obj.toString())
out.write('"')
}
}
else
{
out.write(obj.toString())
}
}
}
private void writeArray(final Object array, final boolean showType)
{
if (writeOptionalReference(array))
{
return;
}
Class arrayType = array.getClass()
int len = Array.getLength(array)
boolean referenced = objsReferenced.containsKey(array)
// boolean typeWritten = showType && !(Object[].class == arrayType) // causes IDE warning in NetBeans 7/4 Java 1.7
boolean typeWritten = showType && !(arrayType.equals(([] as Object[]).class))
final Writer output = this.out // performance opt: place in final local for quicker access
if (typeWritten || referenced)
{
output.write('{')
tabIn()
}
if (referenced)
{
writeId(getId(array))
output.write(',')
newLine()
}
if (typeWritten)
{
writeType(array, output)
output.write(',')
newLine()
}
if (len == 0)
{
if (typeWritten || referenced)
{
output.write(shortMetaKeys ? '"@e":[]' : '"@items":[]')
tabOut()
output.write('}')
}
else
{
output.write("[]")
}
return
}
if (typeWritten || referenced)
{
output.write(shortMetaKeys ? '"@e":[' : '"@items":[')
}
else
{
output.write('[')
}
tabIn()
final int lenMinus1 = len - 1
// Intentionally processing each primitive array type in separate
// custom loop for speed. All of them could be handled using
// reflective Array.get() but it is slower. I chose speed over code length.
if (([] as byte[]).class.is(arrayType))
{
writeByteArray((byte[]) array, lenMinus1)
}
else if (([] as char[]).class.is(arrayType))
{
writeJsonUtf8String(new String((char[]) array), output)
}
else if (([] as short[]).class.is(arrayType))
{
writeShortArray((short[]) array, lenMinus1)
}
else if (([] as int[]).class.is(arrayType))
{
writeIntArray((int[]) array, lenMinus1)
}
else if (([] as long[]).class.is(arrayType))
{
writeLongArray((long[]) array, lenMinus1)
}
else if (([] as float[]).class.is(arrayType))
{
writeFloatArray((float[]) array, lenMinus1)
}
else if (([] as double[]).class.is(arrayType))
{
writeDoubleArray((double[]) array, lenMinus1)
}
else if (([] as boolean[]).class.is(arrayType))
{
writeBooleanArray((boolean[]) array, lenMinus1)
}
else
{
final Class componentClass = array.getClass().componentType
final boolean isPrimitiveArray = MetaUtils.isPrimitive(componentClass)
final boolean isObjectArray = ([] as Object[]).class.is(arrayType)
for (int i = 0; i < len; i++)
{
final Object value = Array.get(array, i)
if (value == null)
{
output.write("null")
}
else if (writeArrayElementIfMatching(componentClass, value, false, output)) { }
else if (isPrimitiveArray || value instanceof Boolean || value instanceof Long || value instanceof Double)
{
writePrimitive(value, value.getClass() != componentClass)
}
else if (isObjectArray)
{
if (writeIfMatching(value, true, output)) { }
else
{
writeImpl(value, true)
}
}
else
{ // Specific Class-type arrays - only force type when
// the instance is derived from array base class.
boolean forceType = !(value.getClass() == componentClass)
writeImpl(value, forceType || alwaysShowType())
}
if (i != lenMinus1)
{
output.write(',')
newLine()
}
}
}
tabOut()
output.write(']')
if (typeWritten || referenced)
{
tabOut()
output.write('}')
}
}
/**
* @return true if the user set the 'TYPE' flag to true, indicating to always show type.
*/
private static boolean alwaysShowType()
{
return Boolean.TRUE.equals(_args.get().containsKey(TYPE))
}
private void writeBooleanArray(boolean[] booleans, int lenMinus1)
{
final Writer output = this.out
for (int i = 0; i < lenMinus1; i++)
{
output.write(booleans[i] ? "true," : "false,")
}
output.write(Boolean.toString(booleans[lenMinus1]))
}
private void writeDoubleArray(double[] doubles, int lenMinus1)
{
final Writer output = this.out;
for (int i = 0; i < lenMinus1; i++)
{
output.write(Double.toString(doubles[i]))
output.write(',')
}
output.write(Double.toString(doubles[lenMinus1]))
}
private void writeFloatArray(float[] floats, int lenMinus1)
{
final Writer output = this.out;
for (int i = 0; i < lenMinus1; i++)
{
output.write(Float.toString(floats[i]))
output.write(',')
}
output.write(Float.toString(floats[lenMinus1]))
}
private void writeLongArray(long[] longs, int lenMinus1) throws IOException
{
final Writer output = this.out
if (getWriteLongsAsStrings())
{
for (int i = 0; i < lenMinus1; i++)
{
output.write('"')
output.write(Long.toString(longs[i]))
output.write('"')
output.write(',')
}
output.write('"')
output.write(Long.toString(longs[lenMinus1]))
output.write('"')
}
else
{
for (int i = 0; i < lenMinus1; i++)
{
output.write(Long.toString(longs[i]))
output.write(',')
}
output.write(Long.toString(longs[lenMinus1]))
}
}
private void writeIntArray(int[] ints, int lenMinus1)
{
final Writer output = this.out;
for (int i = 0; i < lenMinus1; i++)
{
output.write(Integer.toString(ints[i]))
output.write(',')
}
output.write(Integer.toString(ints[lenMinus1]))
}
private void writeShortArray(short[] shorts, int lenMinus1)
{
final Writer output = this.out;
for (int i = 0; i < lenMinus1; i++)
{
output.write(Integer.toString(shorts[i]))
output.write(',')
}
output.write(Integer.toString(shorts[lenMinus1]))
}
private void writeByteArray(byte[] bytes, int lenMinus1)
{
final Writer output = this.out;
final Object[] byteStrs = byteStrings;
for (int i = 0; i < lenMinus1; i++)
{
output.write((char[]) byteStrs[bytes[i] + 128])
output.write(',')
}
output.write((char[]) byteStrs[bytes[lenMinus1] + 128])
}
private void writeCollection(Collection col, boolean showType)
{
if (writeOptionalReference(col))
{
return
}
final Writer output = this.out
boolean referenced = objsReferenced.containsKey(col)
boolean isEmpty = col.isEmpty()
if (referenced || showType)
{
output.write('{')
tabIn()
}
else if (isEmpty)
{
output.write('[')
}
writeIdAndTypeIfNeeded(col, showType, referenced)
if (isEmpty)
{
if (referenced || showType)
{
tabOut()
output.write('}')
}
else
{
output.write(']')
}
return;
}
beginCollection(showType, referenced)
Iterator i = col.iterator()
while (i.hasNext())
{
writeCollectionElement(i.next())
if (i.hasNext())
{
output.write(',')
newLine()
}
}
tabOut()
output.write(']')
if (showType || referenced)
{ // Finished object, as it was output as an object if @id or @type was output
tabOut()
output.write('}')
}
}
private void writeIdAndTypeIfNeeded(Object col, boolean showType, boolean referenced)
{
if (referenced)
{
writeId(getId(col))
}
if (showType)
{
if (referenced)
{
out.write(',')
newLine()
}
writeType(col, out)
}
}
private void beginCollection(boolean showType, boolean referenced)
{
if (showType || referenced)
{
out.write(',')
newLine()
out.write(shortMetaKeys ? '"@e":[' : '"@items":[')
}
else
{
out.write('[')
}
tabIn()
}
private void writeJsonObjectArray(JsonObject jObj, boolean showType)
{
if (writeOptionalReference(jObj))
{
return;
}
int len = jObj.length
String type = jObj.type
Class arrayClass;
if (type == null || ([] as Object[]).class.is(type))
{
arrayClass = ([] as Object[]).class
}
else
{
arrayClass = MetaUtils.classForName(type)
}
final Writer output = this.out
final boolean isObjectArray = ([] as Object[]).class.is(arrayClass)
final Class componentClass = arrayClass.componentType
boolean referenced = objsReferenced.containsKey(jObj) && jObj.hasId()
boolean typeWritten = showType && !isObjectArray
if (typeWritten || referenced)
{
output.write('{')
tabIn()
}
if (referenced)
{
writeId(Long.toString(jObj.id))
output.write(',')
newLine()
}
if (typeWritten)
{
output.write(shortMetaKeys ? '"@t":"' : '"@type":"')
output.write(getSubstituteTypeName(arrayClass.getName()))
output.write('",')
newLine()
}
if (len == 0)
{
if (typeWritten || referenced)
{
output.write(shortMetaKeys ? '"@e":[]' : '"@items":[]')
tabOut()
output.write("}")
}
else
{
output.write("[]")
}
return
}
if (typeWritten || referenced)
{
output.write(shortMetaKeys ? '"@e":[' : '"@items":[')
}
else
{
output.write('[')
}
tabIn()
Object[] items = (Object[]) jObj['@items']
final int lenMinus1 = len - 1
for (int i = 0; i < len; i++)
{
final Object value = items[i]
if (value == null)
{
output.write("null")
}
else if (Character.class == componentClass || char.class == componentClass)
{
writeJsonUtf8String((String) value, output)
}
else if (value instanceof Boolean || value instanceof Long || value instanceof Double)
{
writePrimitive(value, value.getClass() != componentClass)
}
else if (value instanceof String)
{ // Have to specially treat String because it could be referenced, but we still want inline (no @type, value:)
writeJsonUtf8String((String) value, output)
}
else if (isObjectArray)
{
if (writeIfMatching(value, true, output)) { }
else
{
writeImpl(value, true)
}
}
else if (writeArrayElementIfMatching(componentClass, value, false, output)) { }
else
{ // Specific Class-type arrays - only force type when
// the instance is derived from array base class.
boolean forceType = !(value.getClass() == componentClass)
writeImpl(value, forceType || alwaysShowType())
}
if (i != lenMinus1)
{
output.write(',')
newLine()
}
}
tabOut()
output.write(']')
if (typeWritten || referenced)
{
tabOut()
output.write('}')
}
}
private void writeJsonObjectCollection(JsonObject jObj, boolean showType)
{
if (writeOptionalReference(jObj))
{
return
}
String type = jObj.type
Class colClass = MetaUtils.classForName(type)
boolean referenced = objsReferenced.containsKey(jObj) && jObj.hasId()
final Writer output = this.out
int len = jObj.length
if (referenced || showType || len == 0)
{
output.write('{')
tabIn()
}
if (referenced)
{
writeId(String.valueOf(jObj.id))
}
if (showType)
{
if (referenced)
{
output.write(',')
newLine()
}
output.write(shortMetaKeys ? '"@t":"' : '"@type":"')
output.write(getSubstituteTypeName(colClass.getName()))
output.write('"')
}
if (len == 0)
{
tabOut()
output.write('}')
return
}
beginCollection(showType, referenced)
Object[] items = (Object[]) jObj['@items']
final int itemsLen = items.length
final int itemsLenMinus1 = itemsLen - 1
for (int i=0; i < itemsLen; i++)
{
writeCollectionElement(items[i])
if (i != itemsLenMinus1)
{
output.write(',')
newLine()
}
}
tabOut()
output.write("]")
if (showType || referenced)
{
tabOut()
output.write('}')
}
}
private void writeJsonObjectMap(JsonObject jObj, boolean showType)
{
if (writeOptionalReference(jObj))
{
return
}
boolean referenced = objsReferenced.containsKey(jObj) && jObj.hasId()
final Writer output = this.out
output.write('{')
tabIn()
if (referenced)
{
writeId(String.valueOf(jObj.id))
}
if (showType)
{
if (referenced)
{
output.write(',')
newLine()
}
String type = jObj.type
if (type != null)
{
Class mapClass = MetaUtils.classForName(type)
output.write(shortMetaKeys ? '"@t":"' : '"@type":"');
output.write(getSubstituteTypeName(mapClass.getName()));
output.write('"')
}
else
{ // type not displayed
showType = false
}
}
if (jObj.isEmpty())
{ // Empty
tabOut()
output.write('}')
return
}
if (showType)
{
output.write(',')
newLine()
}
output.write(shortMetaKeys ? '"@k":[' : '"@keys":[')
tabIn()
Iterator i = jObj.keySet().iterator()
while (i.hasNext())
{
writeCollectionElement(i.next())
if (i.hasNext())
{
output.write(',')
newLine()
}
}
tabOut()
output.write('],')
newLine()
output.write(shortMetaKeys ? '"@e":[' : '"@items":[')
tabIn()
i =jObj.values().iterator()
while (i.hasNext())
{
writeCollectionElement(i.next())
if (i.hasNext())
{
output.write(',')
newLine()
}
}
tabOut()
output.write(']')
tabOut()
output.write('}')
}
private boolean writeJsonObjectMapWithStringKeys(JsonObject jObj, boolean showType)
{
if (!ensureJsonPrimitiveKeys(jObj))
{
return false
}
if (writeOptionalReference(jObj))
{
return true
}
boolean referenced = objsReferenced.containsKey(jObj) && jObj.hasId()
final Writer output = this.out
output.write('{')
tabIn()
if (referenced)
{
writeId(String.valueOf(jObj.id))
}
if (showType)
{
if(referenced)
{
output.write(',')
newLine()
}
String type = jObj.type
if (type != null)
{
Class mapClass = MetaUtils.classForName(type)
output.write(shortMetaKeys ? '"@t":"' : '"@type":"')
output.write(getSubstituteTypeName(mapClass.getName()))
output.write('"')
}
else
{ // type not displayed
showType = false
}
}
if (jObj.isEmpty())
{ // Empty
tabOut()
output.write('}')
return true
}
if (showType)
{
output.write(',')
newLine()
}
return writeMapBody(jObj.entrySet().iterator())
}
private void writeJsonObjectObject(JsonObject jObj, boolean showType)
{
if (writeOptionalReference(jObj))
{
return;
}
final Writer output = this.out
boolean referenced = objsReferenced.containsKey(jObj) && jObj.hasId()
showType = showType && jObj.getType() != null
Class type = null
output.write('{')
tabIn()
if (referenced)
{
writeId(String.valueOf(jObj.id))
}
if (showType)
{
if (referenced)
{
output.write(',')
newLine()
}
output.write(shortMetaKeys ? '"@t":"' : '"@type":"')
output.write(getSubstituteTypeName(jObj.type))
output.write('"')
try { type = MetaUtils.classForName(jObj.type) }
catch(Exception ignored) { type = null; }
}
if (jObj.isEmpty())
{
tabOut()
output.write('}')
return
}
if (showType || referenced)
{
output.write(',')
newLine()
}
Iterator> i = jObj.entrySet().iterator()
while (i.hasNext())
{
Entryentry = i.next()
final String fieldName = entry.key
output.write('"')
output.write(fieldName)
output.write('":')
Object value = entry.value
if (value == null)
{
output.write("null")
}
else if (value instanceof BigDecimal || value instanceof BigInteger)
{
writeImpl(value, !doesValueTypeMatchFieldType(type, fieldName, value))
}
else if (value instanceof Number || value instanceof Boolean)
{
output.write(value.toString())
}
else if (value instanceof String)
{
writeJsonUtf8String((String) value, output)
}
else if (value instanceof Character)
{
writeJsonUtf8String(String.valueOf(value), output)
}
else
{
writeImpl(value, !doesValueTypeMatchFieldType(type, fieldName, value))
}
if (i.hasNext())
{
output.write(',')
newLine()
}
}
tabOut()
output.write('}')
}
private static boolean doesValueTypeMatchFieldType(Class type, String fieldName, Object value)
{
if (type != null)
{
Map classFields = MetaUtils.getDeepDeclaredFields(type)
Field field = classFields[fieldName]
return field != null && (value.getClass() == field.type)
}
return false
}
private void writeMap(Map map, boolean showType)
{
if (writeOptionalReference(map))
{
return
}
final Writer output = this.out;
boolean referenced = objsReferenced.containsKey(map)
output.write('{')
tabIn()
if (referenced)
{
writeId(getId(map))
}
if (showType)
{
if (referenced)
{
output.write(',')
newLine()
}
writeType(map, output)
}
if (map.isEmpty())
{
tabOut()
output.write('}')
return
}
if (showType || referenced)
{
output.write(',')
newLine()
}
output.write(shortMetaKeys ? '"@k":[' : '"@keys":[')
tabIn()
Iterator i = map.keySet().iterator()
while (i.hasNext())
{
writeCollectionElement(i.next())
if (i.hasNext())
{
output.write(',')
newLine()
}
}
tabOut()
output.write("],")
newLine()
output.write(shortMetaKeys ? '"@e":[' : '"@items":[')
tabIn()
i = map.values().iterator()
while (i.hasNext())
{
writeCollectionElement(i.next())
if (i.hasNext())
{
output.write(',')
newLine()
}
}
tabOut()
output.write(']')
tabOut()
output.write('}')
}
private boolean writeMapWithStringKeys(Map map, boolean showType)
{
if (!ensureJsonPrimitiveKeys(map))
{
return false
}
if (writeOptionalReference(map))
{
return true
}
boolean referenced = objsReferenced.containsKey(map)
out.write('{')
tabIn()
writeIdAndTypeIfNeeded(map, showType, referenced)
if (map.isEmpty())
{
tabOut()
out.write('}')
return true
}
if (showType || referenced)
{
out.write(',')
newLine()
}
return writeMapBody(map.entrySet().iterator())
}
private boolean writeMapBody(final Iterator i)
{
final Writer output = out;
while (i.hasNext())
{
Entry att2value = (Entry) i.next()
output.write('"')
output.write((String) att2value.key)
output.write('":')
writeCollectionElement(att2value.value)
if (i.hasNext())
{
output.write(',')
newLine()
}
}
tabOut()
output.write('}')
return true
}
// Ensure that all keys within the Map are String instances
static boolean ensureJsonPrimitiveKeys(Map map)
{
for (Object o : map.keySet())
{
if (!(o instanceof String))
{
return false
}
}
return true
}
/**
* Write an element that is contained in some type of collection or Map.
* @param o Collection element to output in JSON format.
*/
private void writeCollectionElement(Object o)
{
if (o == null)
{
out.write('null')
}
else if (o instanceof Boolean || o instanceof Double)
{
out.write(o.toString())
}
else if (o instanceof Long)
{
writePrimitive(o, getWriteLongsAsStrings())
}
else if (o instanceof String)
{
writeJsonUtf8String((String) o, out)
}
else
{
writeImpl(o, true)
}
}
/**
* @param obj Object to be written in JSON format
* @param showType boolean true means show the "@type" field, false
* eliminates it. Many times the type can be dropped because it can be
* inferred from the field or array type.
*/
private void writeObject(final Object obj, final boolean showType)
{
if (writeIfMatching(obj, showType, out))
{
return
}
if (writeOptionalReference(obj))
{
return
}
out.write('{')
tabIn()
final boolean referenced = objsReferenced.containsKey(obj)
if (referenced)
{
writeId(getId(obj))
}
if (referenced && showType)
{
out.write(',')
newLine()
}
if (showType)
{
writeType(obj, out)
}
boolean first = !showType;
if (referenced && !showType)
{
first = false
}
final Map> fieldSpecifiers = (Map) _args.get()[FIELD_SPECIFIERS]
final List externallySpecifiedFields = getFieldsUsingSpecifier(obj.getClass(), fieldSpecifiers)
if (externallySpecifiedFields != null)
{ // Caller is using associating a class name to a set of fields for the given class (allows field reductions)
for (Field field : externallySpecifiedFields)
{ // Not currently supporting overwritten field names in hierarchy when using external field specifier
first = writeField(obj, first, field.getName(), field, true)
}
}
else
{ // Reflectively use fields, skipping transient and static fields
final Map classInfo = MetaUtils.getDeepDeclaredFields(obj.getClass())
for (Entry entry : classInfo.entrySet())
{
final String fieldName = entry.key
final Field field = entry.value
first = writeField(obj, first, fieldName, field, false)
}
}
tabOut()
out.write('}')
}
private boolean writeField(Object obj, boolean first, String fieldName, Field field, boolean allowTransient)
{
if (!allowTransient && (field.modifiers & Modifier.TRANSIENT) != 0)
{ // Do not write transient fields
return first
}
int modifiers = field.modifiers
if (field.declaringClass.isEnum() && !Modifier.isPublic(modifiers) && isPublicEnumsOnly())
{
return first
}
if (!first)
{
out.write(',')
newLine()
}
writeJsonUtf8String(fieldName, out)
out.write(':')
Object o
try
{
o = field.get(obj)
}
catch (Exception ignored)
{
o = null
}
if (o == null)
{ // don't quote null
out.write("null")
return false
}
Class type = field.type
boolean forceType = o.getClass() != type; // If types are not exactly the same, write "@type" field
if (MetaUtils.isPrimitive(type))
{
writePrimitive(o, false)
}
else if (writeIfMatching(o, forceType, out)) { }
else
{
writeImpl(o, forceType || alwaysShowType())
}
return false
}
/**
* Write out special characters "\b, \f, \t, \n, \r", as such, backslash as \\
* quote as \" and values less than an ASCII space (20hex) as "\\u00xx" format,
* characters in the range of ASCII space to a '~' as ASCII, and anything higher in UTF-8.
*
* @param s String to be written in utf8 format on the output stream.
*/
static void writeJsonUtf8String(String s, final Writer output)
{
output.write('\"')
final int len = s.length()
for (int i = 0; i < len; i++)
{
char c = s.charAt(i)
if (c < (char)' ')
{ // Anything less than ASCII space, write either in \\u00xx form, or the special \t, \n, etc. form
switch (c)
{
case (char)'\b':
output.write('\\b')
break
case (char)'\f':
output.write('\\f')
break
case (char)'\n':
output.write('\\n')
break
case (char)'\r':
output.write('\\r')
break
case (char)'\t':
output.write('\\t')
break
default:
String hex = Integer.toHexString((int)c)
output.write('\\u')
final int pad = 4 - hex.length()
for (int k = 0; k < pad; k++)
{
output.write('0')
}
output.write(hex)
break
}
}
else if (c == (char)'\\' || c == (char)'"')
{
output.write('\\')
output.write(c)
}
else
{ // Anything else - write in UTF-8 form (multi-byte encoded) (OutputStreamWriter is UTF-8)
output.write(c)
}
}
output.write('\"')
}
void flush()
{
try
{
if (out != null)
{
out.flush()
}
}
catch (Exception ignored) { }
}
void close()
{
try
{
out.close()
}
catch (Exception ignore) { }
}
private String getId(Object o)
{
if (o instanceof JsonObject)
{
Long id = ((JsonObject) o).getId()
if (id != -1)
{
return String.valueOf(id)
}
}
Long id = objsReferenced[o]
return id == null ? null : Long.toString(id)
}
}