Java tutorial
/* * The MIT License * * Copyright 2014 Halcyon <support@planetcrypto.com A2A1A088>. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.json.prettyprint; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.Flushable; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Timestamp; import java.text.Format; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TimeZone; /** * Output a Java 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. * <br/><ul><li> * Call the static method: {@code JsonWriter.toJson(employee)}. This will * convert the passed in 'employee' instance into a JSON String.</li> * <li>Using streams: * <pre> JsonWriter writer = new JsonWriter(stream); * writer.write(employee); * writer.close();</pre> * This will write the 'employee' object to the passed in OutputStream. * </li> * <p>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 * <br/><br/> * <p>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.</p> * <br/> * * @author John DeRegnaucourt (jdereg@gmail.com) * <br/> * Copyright (c) Cedar Software LLC * <br/><br/> * 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 * <br/><br/> * http://www.apache.org/licenses/LICENSE-2.0 * <br/><br/> * 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. */ public class JsonWriter implements Closeable, Flushable { public static final String DATE_FORMAT = "DATE_FORMAT"; public static final String ISO_DATE_FORMAT = "yyyy-MM-dd"; public static final String ISO_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; public static final String TYPE = "TYPE"; public static final String PRETTY_PRINT = "PRETTY_PRINT"; public static final String FIELD_SPECIFIERS = "FIELD_SPECIFIERS"; private static final Map<String, ClassMeta> _classMetaCache = new HashMap<String, ClassMeta>(); private static final List<Object[]> _writers = new ArrayList<Object[]>(); private static final Set<Class> _notCustom = new HashSet<Class>(); private static Object[] _byteStrings = new Object[256]; private static final String newLine = System.getProperty("line.separator"); private final Map<Object, Long> _objVisited = new IdentityHashMap<Object, Long>(); private final Map<Object, Long> _objsReferenced = new IdentityHashMap<Object, Long>(); private final Writer _out; private long _identity = 1; private int depth = 0; // _args is using ThreadLocal so that static inner classes can have access to them static final ThreadLocal<Map<String, Object>> _args = new ThreadLocal<Map<String, Object>>() { public Map<String, Object> initialValue() { return new HashMap<String, Object>(); } }; static final ThreadLocal<SimpleDateFormat> _dateFormat = new ThreadLocal<SimpleDateFormat>() { public SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); } }; static { // Add customer writers (these make common classes more succinct) addWriter(String.class, new JsonStringWriter()); addWriter(Date.class, new DateWriter()); addWriter(BigInteger.class, new BigIntegerWriter()); addWriter(BigDecimal.class, new BigDecimalWriter()); addWriter(java.sql.Date.class, new DateWriter()); addWriter(Timestamp.class, new TimestampWriter()); addWriter(Calendar.class, new CalendarWriter()); addWriter(TimeZone.class, new TimeZoneWriter()); addWriter(Locale.class, new LocaleWriter()); addWriter(Class.class, new ClassWriter()); addWriter(StringBuilder.class, new StringBuilderWriter()); addWriter(StringBuffer.class, new StringBufferWriter()); } static { for (short i = -128; i <= 127; i++) { char[] chars = Integer.toString(i).toCharArray(); _byteStrings[i + 128] = chars; } } static class ClassMeta extends LinkedHashMap<String, Field> { } @Deprecated public static String toJson(Object item) { throw new RuntimeException("Use com.cedarsoftware.util.JsonWriter.objectToJson()"); } @Deprecated public static String toJson(Object item, Map<String, Object> optionalArgs) { throw new RuntimeException("Use com.cedarsoftware.util.JsonWriter.objectToJson()"); } /** * @see JsonWriter#objectToJson(Object, java.util.Map) */ public static String objectToJson(Object item) throws IOException { return objectToJson(item, new HashMap<String, Object>()); } /** * Convert a Java 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. * @throws java.io.IOException If an I/O error occurs */ public static String objectToJson(Object item, Map<String, Object> optionalArgs) throws IOException { ByteArrayOutputStream stream = new ByteArrayOutputStream(); JsonWriter writer = new JsonWriter(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. */ public static String formatJson(String json) throws IOException { Map map = JsonReader.jsonToMaps(json); Map args = new HashMap(); args.put(PRETTY_PRINT, "true"); return objectToJson(map, args); } /** * @see JsonWriter#JsonWriter(java.io.OutputStream, java.util.Map) */ public JsonWriter(OutputStream out) throws IOException { this(out, new HashMap<String, Object>()); } /** * @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)'. * @throws IOException */ public JsonWriter(OutputStream out, Map<String, Object> optionalArgs) throws IOException { Map args = _args.get(); args.clear(); args.putAll(optionalArgs); if (!optionalArgs.containsKey(FIELD_SPECIFIERS)) { // Ensure that at least an empty Map is in the FIELD_SPECIFIERS entry args.put(FIELD_SPECIFIERS, new HashMap()); } else { // Convert String field names to Java Field instances (makes it easier for user to set this up) Map<Class, List<String>> specifiers = (Map<Class, List<String>>) args.get(FIELD_SPECIFIERS); Map<Class, List<Field>> copy = new HashMap<Class, List<Field>>(); for (Map.Entry<Class, List<String>> entry : specifiers.entrySet()) { Class c = entry.getKey(); List<String> fields = entry.getValue(); List<Field> newList = new ArrayList(fields.size()); ClassMeta meta = getDeepDeclaredFields(c); for (String field : fields) { Field f = meta.get(field); if (f == null) { throw new IllegalArgumentException("Unable to locate field: " + field + " on class: " + c.getName() + ". Make sure the fields in the FIELD_SPECIFIERS map existing on the associated class."); } newList.add(f); } copy.put(c, newList); } args.put(FIELD_SPECIFIERS, copy); } try { _out = new BufferedWriter(new OutputStreamWriter(out, "UTF-8")); } catch (UnsupportedEncodingException e) { throw new IOException("Unsupported encoding. Get a JVM that supports UTF-8", e); } } public interface JsonClassWriter { void write(Object o, boolean showType, Writer out) throws IOException; boolean hasPrimitiveForm(); void writePrimitiveForm(Object o, Writer out) throws IOException; } public boolean isPrettyPrint() { Object setting = _args.get().get(PRETTY_PRINT); 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; } private void tabIn(Writer out) throws IOException { if (!isPrettyPrint()) { return; } out.write(newLine); depth++; for (int i = 0; i < depth; i++) { out.write(" "); } } private void newLine(Writer out) throws IOException { if (!isPrettyPrint()) { return; } out.write(newLine); for (int i = 0; i < depth; i++) { out.write(" "); } } private void tabOut(Writer out) throws IOException { if (!isPrettyPrint()) { return; } out.write(newLine); depth--; for (int i = 0; i < depth; i++) { out.write(" "); } } public static int getDistance(Class a, Class b) { Class curr = b; int distance = 0; while (curr != a) { distance++; curr = curr.getSuperclass(); if (curr == null) { return Integer.MAX_VALUE; } } return distance; } public boolean writeIfMatching(Object o, boolean showType, Writer out) throws IOException { Class c = o.getClass(); if (_notCustom.contains(c)) { return false; } return writeCustom(c, o, showType, out); } public boolean writeArrayElementIfMatching(Class arrayComponentClass, Object o, boolean showType, Writer out) throws IOException { if (!o.getClass().isAssignableFrom(arrayComponentClass) || _notCustom.contains(o.getClass())) { return false; } return writeCustom(arrayComponentClass, o, showType, out); } private boolean writeCustom(Class arrayComponentClass, Object o, boolean showType, Writer out) throws IOException { JsonClassWriter closestWriter = null; int minDistance = Integer.MAX_VALUE; for (Object[] item : _writers) { Class clz = (Class) item[0]; if (clz == arrayComponentClass) { closestWriter = (JsonClassWriter) item[1]; break; } int distance = getDistance(clz, arrayComponentClass); if (distance < minDistance) { minDistance = distance; closestWriter = (JsonClassWriter) item[1]; } } if (closestWriter == null) { return false; } if (writeOptionalReference(o)) { return true; } boolean referenced = _objsReferenced.containsKey(o); if ((!referenced && !showType && closestWriter.hasPrimitiveForm()) || closestWriter instanceof JsonStringWriter) { closestWriter.writePrimitiveForm(o, out); return true; } out.write('{'); tabIn(out); if (referenced) { writeId(getId(o)); if (showType) { out.write(','); newLine(out); } } if (showType) { writeType(o, out); } if (referenced || showType) { out.write(','); newLine(out); } closestWriter.write(o, showType || referenced, out); tabOut(out); out.write('}'); return true; } public static void addWriter(Class c, JsonClassWriter writer) { for (Object[] item : _writers) { Class clz = (Class) item[0]; if (clz == c) { item[1] = writer; // Replace writer return; } } _writers.add(new Object[] { c, writer }); } public static void addNotCustomWriter(Class c) { _notCustom.add(c); } public static class TimeZoneWriter implements JsonClassWriter { public void write(Object obj, boolean showType, Writer out) throws IOException { TimeZone cal = (TimeZone) obj; out.write("\"zone\":\""); out.write(cal.getID()); out.write('"'); } public boolean hasPrimitiveForm() { return false; } public void writePrimitiveForm(Object o, Writer out) throws IOException { } } public static class CalendarWriter implements JsonClassWriter { public void write(Object obj, boolean showType, Writer out) throws IOException { Calendar cal = (Calendar) obj; _dateFormat.get().setTimeZone(cal.getTimeZone()); out.write("\"time\":\""); out.write(_dateFormat.get().format(cal.getTime())); out.write("\",\"zone\":\""); out.write(cal.getTimeZone().getID()); out.write('"'); } public boolean hasPrimitiveForm() { return false; } public void writePrimitiveForm(Object o, Writer out) throws IOException { } } public static class DateWriter implements JsonClassWriter { public void write(Object obj, boolean showType, Writer out) throws IOException { Date date = (Date) obj; Object dateFormat = _args.get().get(DATE_FORMAT); if (dateFormat instanceof String) { // Passed in as String, turn into a SimpleDateFormat instance to be used throughout this stream write. dateFormat = new SimpleDateFormat((String) dateFormat, Locale.ENGLISH); _args.get().put(DATE_FORMAT, dateFormat); } if (showType) { out.write("\"value\":"); } if (dateFormat instanceof Format) { out.write("\""); out.write(((Format) dateFormat).format(date)); out.write("\""); } else { out.write(Long.toString(((Date) obj).getTime())); } } public boolean hasPrimitiveForm() { return true; } public void writePrimitiveForm(Object o, Writer out) throws IOException { if (_args.get().containsKey(DATE_FORMAT)) { write(o, false, out); } else { out.write(Long.toString(((Date) o).getTime())); } } } public static class TimestampWriter implements JsonClassWriter { public void write(Object o, boolean showType, Writer out) throws IOException { Timestamp tstamp = (Timestamp) o; out.write("\"time\":\""); out.write(Long.toString((tstamp.getTime() / 1000) * 1000)); out.write("\",\"nanos\":\""); out.write(Integer.toString(tstamp.getNanos())); out.write('"'); } public boolean hasPrimitiveForm() { return false; } public void writePrimitiveForm(Object o, Writer out) throws IOException { } } public static class ClassWriter implements JsonClassWriter { public void write(Object obj, boolean showType, Writer out) throws IOException { String value = ((Class) obj).getName(); out.write("\"value\":"); writeJsonUtf8String(value, out); } public boolean hasPrimitiveForm() { return true; } public void writePrimitiveForm(Object o, Writer out) throws IOException { writeJsonUtf8String(((Class) o).getName(), out); } } public static class JsonStringWriter implements JsonClassWriter { public void write(Object obj, boolean showType, Writer out) throws IOException { out.write("\"value\":"); writeJsonUtf8String((String) obj, out); } public boolean hasPrimitiveForm() { return true; } public void writePrimitiveForm(Object o, Writer out) throws IOException { writeJsonUtf8String((String) o, out); } } public static class LocaleWriter implements JsonClassWriter { public void write(Object obj, boolean showType, Writer out) throws IOException { Locale locale = (Locale) obj; out.write("\"language\":\""); out.write(locale.getLanguage()); out.write("\",\"country\":\""); out.write(locale.getCountry()); out.write("\",\"variant\":\""); out.write(locale.getVariant()); out.write('"'); } public boolean hasPrimitiveForm() { return false; } public void writePrimitiveForm(Object o, Writer out) throws IOException { } } public static class BigIntegerWriter implements JsonClassWriter { public void write(Object obj, boolean showType, Writer out) throws IOException { BigInteger big = (BigInteger) obj; out.write("\"value\":\""); out.write(big.toString(10)); out.write('"'); } public boolean hasPrimitiveForm() { return true; } public void writePrimitiveForm(Object o, Writer out) throws IOException { BigInteger big = (BigInteger) o; out.write('"'); out.write(big.toString(10)); out.write('"'); } } public static class BigDecimalWriter implements JsonClassWriter { public void write(Object obj, boolean showType, Writer out) throws IOException { BigDecimal big = (BigDecimal) obj; out.write("\"value\":\""); out.write(big.toPlainString()); out.write('"'); } public boolean hasPrimitiveForm() { return true; } public void writePrimitiveForm(Object o, Writer out) throws IOException { BigDecimal big = (BigDecimal) o; out.write('"'); out.write(big.toPlainString()); out.write('"'); } } public static class StringBuilderWriter implements JsonClassWriter { public void write(Object obj, boolean showType, Writer out) throws IOException { StringBuilder builder = (StringBuilder) obj; out.write("\"value\":\""); out.write(builder.toString()); out.write('"'); } public boolean hasPrimitiveForm() { return true; } public void writePrimitiveForm(Object o, Writer out) throws IOException { StringBuilder builder = (StringBuilder) o; out.write('"'); out.write(builder.toString()); out.write('"'); } } public static class StringBufferWriter implements JsonClassWriter { public void write(Object obj, boolean showType, Writer out) throws IOException { StringBuffer buffer = (StringBuffer) obj; out.write("\"value\":\""); out.write(buffer.toString()); out.write('"'); } public boolean hasPrimitiveForm() { return true; } public void writePrimitiveForm(Object o, Writer out) throws IOException { StringBuffer buffer = (StringBuffer) o; out.write('"'); out.write(buffer.toString()); out.write('"'); } } public void write(Object obj) throws IOException { traceReferences(obj); _objVisited.clear(); writeImpl(obj, true); flush(); _objVisited.clear(); _objsReferenced.clear(); _args.get().clear(); _args.remove(); } private void traceReferences(Object root) { LinkedList<Object> stack = new LinkedList<Object>(); stack.addFirst(root); final Map<Object, Long> visited = _objVisited; final Map<Object, Long> referenced = _objsReferenced; while (!stack.isEmpty()) { Object obj = stack.removeFirst(); if (obj == null) { continue; } if (!JsonReader.isPrimitive(obj.getClass()) && !(obj instanceof String) && !(obj instanceof Date)) { Long id = visited.get(obj); if (id != null) { // Only write an object once. referenced.put(obj, id); continue; } visited.put(obj, _identity++); } final Class clazz = obj.getClass(); if (clazz.isArray()) { Class compType = clazz.getComponentType(); if (!JsonReader.isPrimitive(compType) && compType != String.class && !Date.class.isAssignableFrom(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 { 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. */ private static void traceFields(LinkedList<Object> stack, Object obj) { final ClassMeta fields = getDeepDeclaredFields(obj.getClass()); Map<Class, List<Field>> fieldSpecifiers = (Map) _args.get().get(FIELD_SPECIFIERS); // If caller has special Field specifier for a given class // then use it, otherwise use reflection. List<Field> fieldSet = getFieldsUsingSpecifier(obj.getClass(), fieldSpecifiers); if (fieldSet != null) { // Trace fields using external field specifier (explicitly tells us which fields to use for a given class) for (Field field : fieldSet) { traceField(stack, obj, field); } } else { // Trace fields using reflection for (Field field : fields.values()) { traceField(stack, obj, field); } } } /** * Push object associated to field onto stack for further tracing. If object was a primitive, * Date, String, or null, no further tracing is done. */ private static void traceField(LinkedList<Object> stack, Object obj, Field field) { try { final Class<?> type = field.getType(); if (JsonReader.isPrimitive(type) || String.class == type || Date.class.isAssignableFrom(type)) { // speed up: primitives (Dates/Strings considered primitive by json-io) cannot reference another object return; } Object o = field.get(obj); if (o != null) { stack.addFirst(o); } } catch (Exception ignored) { } } private static List<Field> getFieldsUsingSpecifier(Class classBeingWritten, Map<Class, List<Field>> fieldSpecifiers) { Iterator<Map.Entry<Class, List<Field>>> i = fieldSpecifiers.entrySet().iterator(); int minDistance = Integer.MAX_VALUE; List<Field> fields = null; while (i.hasNext()) { Map.Entry<Class, List<Field>> entry = i.next(); Class c = entry.getKey(); if (c == classBeingWritten) { return entry.getValue(); } int distance = getDistance(c, classBeingWritten); if (distance < minDistance) { minDistance = distance; fields = entry.getValue(); } } return fields; } private boolean writeOptionalReference(Object obj) throws IOException { final Writer out = _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; } out.write("{\"@ref\":"); out.write(id); out.write('}'); return true; } // Mark the object as visited by putting it in the Map (this map is re-used / clear()'d after walk()). _objVisited.put(obj, null); return false; } private void writeImpl(Object obj, boolean showType) throws IOException { 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 identical JSON format. JsonObject jObj = (JsonObject) obj; if (jObj.isArray()) { writeJsonObjectArray(jObj, showType); } else if (jObj.isCollection()) { writeJsonObjectCollection(jObj, showType); } else if (jObj.isMap()) { writeJsonObjectMap(jObj, showType); } else { writeJsonObjectObject(jObj, showType); } } else if (obj instanceof Map) { writeMap((Map) obj, showType); } else { writeObject(obj, showType); } } private void writeId(String id) throws IOException { _out.write("\"@id\":"); _out.write(id == null ? "0" : id); } private static void writeType(Object obj, Writer out) throws IOException { out.write("\"@type\":\""); Class c = obj.getClass(); if (Boolean.class == c) { out.write("boolean"); } else if (Byte.class == c) { out.write("byte"); } else if (Short.class == c) { out.write("short"); } else if (Integer.class == c) { out.write("int"); } else if (Long.class == c) { out.write("long"); } else if (Double.class == c) { out.write("double"); } else if (Float.class == c) { out.write("float"); } else if (Character.class == c) { out.write("char"); } else if (Date.class == c) { out.write("date"); } else if (Class.class == c) { out.write("class"); } else if (String.class == c) { out.write("string"); } else { out.write(c.getName()); } out.write('"'); } private void writePrimitive(Object obj) throws IOException { if (obj instanceof Character) { writeJsonUtf8String(String.valueOf(obj), _out); } else { _out.write(obj.toString()); } } private void writeArray(Object array, boolean showType) throws IOException { 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(Object[].class)); final Writer out = _out; // performance opt: place in final local for quicker access if (typeWritten || referenced) { out.write('{'); tabIn(out); } if (referenced) { writeId(getId(array)); out.write(','); newLine(out); } if (typeWritten) { writeType(array, out); out.write(','); newLine(out); } if (len == 0) { if (typeWritten || referenced) { out.write("\"@items\":[]"); tabOut(out); out.write('}'); } else { out.write("[]"); } return; } if (typeWritten || referenced) { out.write("\"@items\":["); } else { out.write('['); } tabIn(out); 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 (byte[].class == arrayType) { writeByteArray((byte[]) array, lenMinus1); } else if (char[].class == arrayType) { writeJsonUtf8String(new String((char[]) array), out); } else if (short[].class == arrayType) { writeShortArray((short[]) array, lenMinus1); } else if (int[].class == arrayType) { writeIntArray((int[]) array, lenMinus1); } else if (long[].class == arrayType) { writeLongArray((long[]) array, lenMinus1); } else if (float[].class == arrayType) { writeFloatArray((float[]) array, lenMinus1); } else if (double[].class == arrayType) { writeDoubleArray((double[]) array, lenMinus1); } else if (boolean[].class == arrayType) { writeBooleanArray((boolean[]) array, lenMinus1); } else { final Class componentClass = array.getClass().getComponentType(); final boolean isPrimitiveArray = JsonReader.isPrimitive(componentClass); final boolean isObjectArray = Object[].class == arrayType; for (int i = 0; i < len; i++) { final Object value = Array.get(array, i); if (value == null) { out.write("null"); } else if (isPrimitiveArray || value instanceof Boolean || value instanceof Long || value instanceof Double) { writePrimitive(value); } else if (writeArrayElementIfMatching(componentClass, value, false, out)) { } else if (isObjectArray) { if (writeIfMatching(value, true, out)) { } 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) { out.write(','); newLine(out); } } } tabOut(out); out.write(']'); if (typeWritten || referenced) { tabOut(out); out.write('}'); } } /** * @return true if the user set the 'TYPE' flag to true, indicating to always show type. */ private boolean alwaysShowType() { return Boolean.TRUE.equals(_args.get().get(TYPE)); } private void writeBooleanArray(boolean[] booleans, int lenMinus1) throws IOException { final Writer out = _out; for (int i = 0; i < lenMinus1; i++) { out.write(booleans[i] ? "true," : "false,"); } out.write(Boolean.toString(booleans[lenMinus1])); } private void writeDoubleArray(double[] doubles, int lenMinus1) throws IOException { final Writer out = _out; for (int i = 0; i < lenMinus1; i++) { out.write(Double.toString(doubles[i])); out.write(','); } out.write(Double.toString(doubles[lenMinus1])); } private void writeFloatArray(float[] floats, int lenMinus1) throws IOException { final Writer out = _out; for (int i = 0; i < lenMinus1; i++) { out.write(Double.toString(floats[i])); out.write(','); } out.write(Float.toString(floats[lenMinus1])); } private void writeLongArray(long[] longs, int lenMinus1) throws IOException { final Writer out = _out; for (int i = 0; i < lenMinus1; i++) { out.write(Long.toString(longs[i])); out.write(','); } out.write(Long.toString(longs[lenMinus1])); } private void writeIntArray(int[] ints, int lenMinus1) throws IOException { final Writer out = _out; for (int i = 0; i < lenMinus1; i++) { out.write(Integer.toString(ints[i])); out.write(','); } out.write(Integer.toString(ints[lenMinus1])); } private void writeShortArray(short[] shorts, int lenMinus1) throws IOException { final Writer out = _out; for (int i = 0; i < lenMinus1; i++) { out.write(Integer.toString(shorts[i])); out.write(','); } out.write(Integer.toString(shorts[lenMinus1])); } private void writeByteArray(byte[] bytes, int lenMinus1) throws IOException { final Writer out = _out; final Object[] byteStrs = _byteStrings; for (int i = 0; i < lenMinus1; i++) { out.write((char[]) byteStrs[bytes[i] + 128]); out.write(','); } out.write((char[]) byteStrs[bytes[lenMinus1] + 128]); } private void writeCollection(Collection col, boolean showType) throws IOException { if (writeOptionalReference(col)) { return; } final Writer out = _out; boolean referenced = _objsReferenced.containsKey(col); boolean isEmpty = col.isEmpty(); if (referenced || showType) { out.write('{'); tabIn(out); } else if (isEmpty) { out.write('['); } if (referenced) { writeId(getId(col)); } if (showType) { if (referenced) { out.write(','); newLine(out); } writeType(col, out); } if (isEmpty) { if (referenced || showType) { tabOut(out); out.write('}'); } else { out.write(']'); } return; } if (showType || referenced) { out.write(','); newLine(out); out.write("\"@items\":["); } else { out.write('['); } tabIn(out); Iterator i = col.iterator(); while (i.hasNext()) { writeCollectionElement(i.next()); if (i.hasNext()) { out.write(','); newLine(out); } } tabOut(out); out.write(']'); if (showType || referenced) { // Finished object, as it was output as an object if @id or @type was output tabOut(out); out.write("}"); } } private void writeJsonObjectArray(JsonObject jObj, boolean showType) throws IOException { if (writeOptionalReference(jObj)) { return; } int len = jObj.getLength(); String type = jObj.type; Class arrayClass; if (type == null || Object[].class.getName().equals(type)) { arrayClass = Object[].class; } else { arrayClass = JsonReader.classForName2(type); } final Writer out = _out; final boolean isObjectArray = Object[].class == arrayClass; final Class componentClass = arrayClass.getComponentType(); boolean referenced = _objsReferenced.containsKey(jObj) && jObj.hasId(); boolean typeWritten = showType && !isObjectArray; if (typeWritten || referenced) { out.write('{'); tabIn(out); } if (referenced) { writeId(Long.toString(jObj.id)); out.write(','); newLine(out); } if (typeWritten) { out.write("\"@type\":\""); out.write(arrayClass.getName()); out.write("\","); newLine(out); } if (len == 0) { if (typeWritten || referenced) { out.write("\"@items\":[]"); tabOut(out); out.write("}"); } else { out.write("[]"); } return; } if (typeWritten || referenced) { out.write("\"@items\":["); } else { out.write('['); } tabIn(out); Object[] items = (Object[]) jObj.get("@items"); final int lenMinus1 = len - 1; for (int i = 0; i < len; i++) { final Object value = items[i]; if (value == null) { out.write("null"); } else if (Character.class == componentClass || char.class == componentClass) { writeJsonUtf8String((String) value, out); } else if (value instanceof Boolean || value instanceof Long || value instanceof Double) { writePrimitive(value); } 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, out); } else if (isObjectArray) { if (writeIfMatching(value, true, out)) { } else { writeImpl(value, true); } } else if (writeArrayElementIfMatching(componentClass, value, false, out)) { } 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) { out.write(','); newLine(out); } } tabOut(out); out.write(']'); if (typeWritten || referenced) { tabOut(out); out.write('}'); } } private void writeJsonObjectCollection(JsonObject jObj, boolean showType) throws IOException { if (writeOptionalReference(jObj)) { return; } String type = jObj.type; Class colClass = JsonReader.classForName2(type); boolean referenced = _objsReferenced.containsKey(jObj) && jObj.hasId(); final Writer out = _out; int len = jObj.getLength(); if (referenced || showType || len == 0) { out.write('{'); tabIn(out); } if (referenced) { writeId(String.valueOf(jObj.id)); } if (showType) { if (referenced) { out.write(','); newLine(out); } out.write("\"@type\":\""); out.write(colClass.getName()); out.write('"'); } if (len == 0) { tabOut(out); out.write('}'); return; } if (showType || referenced) { out.write(','); newLine(out); out.write("\"@items\":["); } else { out.write('['); } tabIn(out); Object[] items = (Object[]) jObj.get("@items"); final int itemsLen = items.length; final int itemsLenMinus1 = itemsLen - 1; for (int i = 0; i < itemsLen; i++) { writeCollectionElement(items[i]); if (i != itemsLenMinus1) { out.write(','); newLine(out); } } tabOut(out); out.write("]"); if (showType || referenced) { tabOut(out); out.write('}'); } } private void writeJsonObjectMap(JsonObject jObj, boolean showType) throws IOException { if (writeOptionalReference(jObj)) { return; } boolean referenced = _objsReferenced.containsKey(jObj) && jObj.hasId(); final Writer out = _out; out.write('{'); tabIn(out); if (referenced) { writeId(String.valueOf(jObj.getId())); } if (showType) { if (referenced) { out.write(','); newLine(out); } String type = jObj.getType(); if (type != null) { Class mapClass = JsonReader.classForName2(type); out.write("\"@type\":\""); out.write(mapClass.getName()); out.write('"'); } else { // type not displayed showType = false; } } if (jObj.isEmpty()) { // Empty tabOut(out); out.write('}'); return; } if (showType) { out.write(','); newLine(out); } out.write("\"@keys\":["); tabIn(out); Iterator i = jObj.keySet().iterator(); while (i.hasNext()) { writeCollectionElement(i.next()); if (i.hasNext()) { out.write(','); newLine(out); } } tabOut(out); out.write("],"); newLine(out); out.write("\"@items\":["); tabIn(out); i = jObj.values().iterator(); while (i.hasNext()) { writeCollectionElement(i.next()); if (i.hasNext()) { out.write(','); newLine(out); } } tabOut(out); out.write(']'); tabOut(out); out.write('}'); } private void writeJsonObjectObject(JsonObject jObj, boolean showType) throws IOException { if (writeOptionalReference(jObj)) { return; } final Writer out = _out; boolean referenced = _objsReferenced.containsKey(jObj) && jObj.hasId(); showType = showType && jObj.type != null; Class type = null; out.write('{'); tabIn(out); if (referenced) { writeId(String.valueOf(jObj.id)); } if (showType) { if (referenced) { out.write(','); newLine(out); } out.write("\"@type\":\""); out.write(jObj.type); out.write('"'); try { type = JsonReader.classForName2(jObj.type); } catch (Exception ignored) { type = null; } } if (jObj.isEmpty()) { tabOut(out); out.write('}'); return; } if (showType || referenced) { out.write(','); newLine(out); } Iterator<Map.Entry<String, Object>> i = jObj.entrySet().iterator(); while (i.hasNext()) { Map.Entry<String, Object> entry = i.next(); final String fieldName = entry.getKey(); out.write('"'); out.write(fieldName); out.write("\":"); Object value = entry.getValue(); if (value == null) { out.write("null"); } else if (value instanceof BigDecimal || value instanceof BigInteger) { writeImpl(value, !doesValueTypeMatchFieldType(type, fieldName, value)); } else if (value instanceof Number || value instanceof Boolean) { out.write(value.toString()); } else if (value instanceof String) { writeJsonUtf8String((String) value, out); } else if (value instanceof Character) { writeJsonUtf8String(String.valueOf(value), out); } else { writeImpl(value, !doesValueTypeMatchFieldType(type, fieldName, value)); } if (i.hasNext()) { out.write(','); newLine(out); } } tabOut(out); out.write('}'); } private static boolean doesValueTypeMatchFieldType(Class type, String fieldName, Object value) { if (type != null) { ClassMeta meta = getDeepDeclaredFields(type); Field field = meta.get(fieldName); return field != null && (value.getClass() == field.getType()); } return false; } private void writeMap(Map map, boolean showType) throws IOException { if (writeOptionalReference(map)) { return; } final Writer out = _out; boolean referenced = _objsReferenced.containsKey(map); out.write('{'); tabIn(out); if (referenced) { writeId(getId(map)); } if (showType) { if (referenced) { out.write(','); newLine(out); } writeType(map, out); } if (map.isEmpty()) { tabOut(out); out.write('}'); return; } if (showType || referenced) { out.write(','); newLine(out); } out.write("\"@keys\":["); tabIn(out); Iterator i = map.keySet().iterator(); while (i.hasNext()) { writeCollectionElement(i.next()); if (i.hasNext()) { out.write(','); newLine(out); } } tabOut(out); out.write("],"); newLine(out); out.write("\"@items\":["); tabIn(out); i = map.values().iterator(); while (i.hasNext()) { writeCollectionElement(i.next()); if (i.hasNext()) { out.write(','); newLine(out); } } tabOut(out); out.write(']'); tabOut(out); out.write('}'); } /** * Write an element that is contained in some type of collection or Map. * @param o Collection element to output in JSON format. * @throws IOException if an error occurs writing to the output stream. */ private void writeCollectionElement(Object o) throws IOException { if (o == null) { _out.write("null"); } else if (o instanceof Boolean || o instanceof Long || o instanceof Double) { _out.write(o.toString()); } 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. * @throws IOException if an error occurs writing to the output stream. */ private void writeObject(Object obj, boolean showType) throws IOException { if (writeIfMatching(obj, showType, _out)) { return; } if (writeOptionalReference(obj)) { return; } final Writer out = _out; out.write('{'); tabIn(out); boolean referenced = _objsReferenced.containsKey(obj); if (referenced) { writeId(getId(obj)); } ClassMeta classInfo = getDeepDeclaredFields(obj.getClass()); if (referenced && showType) { out.write(','); newLine(out); } if (showType) { writeType(obj, out); } boolean first = !showType; if (referenced && !showType) { first = false; } Map<Class, List<Field>> fieldSpecifiers = (Map) _args.get().get(FIELD_SPECIFIERS); List<Field> 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, out, first, field.getName(), field); } } else { // Reflectively use fields, skipping transient and static fields for (Map.Entry<String, Field> entry : classInfo.entrySet()) { String fieldName = entry.getKey(); Field field = entry.getValue(); first = writeField(obj, out, first, fieldName, field); } } tabOut(out); out.write('}'); } private boolean writeField(Object obj, Writer out, boolean first, String fieldName, Field field) throws IOException { if ((field.getModifiers() & Modifier.TRANSIENT) != 0) { // Do not write transient fields return first; } if (first) { first = false; } else { out.write(','); newLine(out); } 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 first; } Class type = field.getType(); boolean forceType = o.getClass() != type; // If types are not exactly the same, write "@type" field if (JsonReader.isPrimitive(type)) { writePrimitive(o); } else if (writeIfMatching(o, forceType, out)) { } else { writeImpl(o, forceType || alwaysShowType()); } return first; } /** * 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. * @throws IOException if an error occurs writing to the output stream. */ public static void writeJsonUtf8String(String s, Writer out) throws IOException { out.write('\"'); int len = s.length(); for (int i = 0; i < len; i++) { char c = s.charAt(i); if (c < ' ') { // Anything less than ASCII space, write either in \\u00xx form, or the special \t, \n, etc. form if (c == '\b') { out.write("\\b"); } else if (c == '\t') { out.write("\\t"); } else if (c == '\n') { out.write("\\n"); } else if (c == '\f') { out.write("\\f"); } else if (c == '\r') { out.write("\\r"); } else { String hex = Integer.toHexString(c); out.write("\\u"); int pad = 4 - hex.length(); for (int k = 0; k < pad; k++) { out.write('0'); } out.write(hex); } } else if (c == '\\' || c == '"') { out.write('\\'); out.write(c); } else { // Anything else - write in UTF-8 form (multi-byte encoded) (OutputStreamWriter is UTF-8) out.write(c); } } out.write('\"'); } /** * @param c Class instance * @return ClassMeta which contains fields of class. The results are cached internally for performance * when called again with same Class. */ static ClassMeta getDeepDeclaredFields(Class c) { ClassMeta classInfo = _classMetaCache.get(c.getName()); if (classInfo != null) { return classInfo; } classInfo = new ClassMeta(); Class curr = c; while (curr != null) { try { Field[] local = curr.getDeclaredFields(); for (Field field : local) { if ((field.getModifiers() & Modifier.STATIC) == 0) { // speed up: do not process static fields. if (!field.isAccessible()) { try { field.setAccessible(true); } catch (Exception ignored) { } } if (classInfo.containsKey(field.getName())) { classInfo.put(curr.getName() + '.' + field.getName(), field); } else { classInfo.put(field.getName(), field); } } } } catch (ThreadDeath t) { throw t; } catch (Throwable ignored) { } curr = curr.getSuperclass(); } _classMetaCache.put(c.getName(), classInfo); return classInfo; } public void flush() { try { if (_out != null) { _out.flush(); } } catch (Exception ignored) { } } public void close() { try { _out.close(); } catch (Exception ignore) { } } private String getId(Object o) { if (o instanceof JsonObject) { long id = ((JsonObject) o).id; if (id != -1) { return String.valueOf(id); } } Long id = _objsReferenced.get(o); return id == null ? null : Long.toString(id); } }