Java tutorial
/******************************************************************************* * ENdoSnipe 5.0 - (https://github.com/endosnipe) * * The MIT License (MIT) * * Copyright (c) 2012 Acroquest Technology Co.,Ltd. * * 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 jp.co.acroquest.jsonic; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Serializable; import java.io.Writer; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.sql.Struct; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.MessageFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.RandomAccess; import java.util.ResourceBundle; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TimeZone; import java.util.TreeMap; import java.util.TreeSet; import java.util.UUID; import java.util.regex.Pattern; import jp.co.acroquest.jsonic.io.AppendableOutputSource; import jp.co.acroquest.jsonic.io.CharSequenceInputSource; import jp.co.acroquest.jsonic.io.InputSource; import jp.co.acroquest.jsonic.io.OutputSource; import jp.co.acroquest.jsonic.io.ReaderInputSource; import jp.co.acroquest.jsonic.io.StringBuilderOutputSource; import jp.co.acroquest.jsonic.io.WriterOutputSource; import jp.co.acroquest.jsonic.util.BeanInfo; import jp.co.acroquest.jsonic.util.ClassUtil; import jp.co.acroquest.jsonic.util.ExtendedDateFormat; import jp.co.acroquest.jsonic.util.PropertyInfo; import org.w3c.dom.CharacterData; import org.w3c.dom.Comment; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * <p>The JSONIC JSON class provides JSON encoding and decoding as * defined by RFC 4627.</p> * * <p>The following example illustrates how to encode and decode. The code:</p> * <pre> * // encodes a object into a json string. * String s = JSON.encode(o); * * // decodes a json string into a object. * Object o = JSON.decode(s); * * // decodes a json string into a typed object. * Foo foo = JSON.decode(s, Foo.class); * </pre> * * <p>Advanced topic:</p> * <pre> * // formats a object into a json string with indents for debug. * JSON json = new JSON(); * json.setPrettyPrint(true); * String pretty = json.format(o); * * //uses Reader/InputStream * Bar bar = JSON.decode(new FileInputStream("bar.json"), Bar.class); * Bar bar = JSON.decode(new FileReader("bar.json"), Bar.class); * </pre> * * <h4>Summary of encoding rules for java type into json type</h4> * <table border="1" cellpadding="1" cellspacing="0"> * <tr> * <th bgcolor="#CCCCFF" align="left">java type</th> * <th bgcolor="#CCCCFF" align="left">json type</th> * </tr> * <tr><td>java.util.Map</td><td rowspan="2">object</td></tr> * <tr><td>java.lang.Object (public property or field)</td></tr> * <tr><td>java.lang.Object[]</td><td rowspan="3">array</td></tr> * <tr><td>java.util.Collection</td></tr> * <tr><td>boolean[], short[], int[], long[], float[], double[]</td></tr> * <tr><td>java.lang.CharSequence</td><td rowspan="10">string</td></tr> * <tr><td>char[]</td></tr> * <tr><td>java.lang.Character</td></tr> * <tr><td>char</td></tr> * <tr><td>java.util.TimeZone</td></tr> * <tr><td>java.util.regex.Pattern</td></tr> * <tr><td>java.lang.reflect.Type</td></tr> * <tr><td>java.lang.reflect.Member</td></tr> * <tr><td>java.net.URI</td></tr> * <tr><td>java.net.URL</td></tr> * <tr><td>byte[]</td><td>string (base64)</td></tr> * <tr><td>java.util.Locale</td><td>string (language-country)</td></tr> * <tr><td>java.lang.Number</td><td rowspan="2">number</td></tr> * <tr><td>byte, short, int, long, float, double</td></tr> * <tr><td>java.util.Date</td><td rowspan="2">number (milliseconds since 1970)</td></tr> * <tr><td>java.util.Calendar</td></tr> * <tr><td>java.lang.Boolean</td><td rowspan="2">true/false</td></tr> * <tr><td>boolean</td></tr> * <tr><td>null</td><td>null</td></tr> * </table> * * <h4>Summary of decoding rules for json type into java type</h4> * <table border="1" cellpadding="1" cellspacing="0"> * <tr> * <th bgcolor="#CCCCFF" align="left">json type</th> * <th bgcolor="#CCCCFF" align="left">java type</th> * </tr> * <tr><td>object</td><td>java.util.LinkedHashMap</td></tr> * <tr><td>array</td><td>java.util.ArrayList</td></tr> * <tr><td>string</td><td>java.lang.String</td></tr> * <tr><td>number</td><td>java.math.BigDecimal</td></tr> * <tr><td>true/false</td><td>java.lang.Boolean</td></tr> * <tr><td>null</td><td>null</td></tr> * </table> * * @author Hidekatsu Izuno * @version 1.2.10 * @see <a href="http://www.rfc-editor.org/rfc/rfc4627.txt">RFC 4627</a> * @see <a href="http://www.apache.org/licenses/LICENSE-2.0">the Apache License, Version 2.0</a> */ public class JSON { /** * JSON processing mode */ public enum Mode { /** * Traditional Mode */ TRADITIONAL, /** * Strict Mode */ STRICT, /** * Script(=JavaScript) Mode */ SCRIPT } /** * Setup your custom class for using static method. default: net.arnx.jsonic.JSON */ public static volatile Class<? extends JSON> prototype = JSON.class; private static final Map<Class<?>, Formatter> FORMAT_MAP = new HashMap<Class<?>, Formatter>(50); private static final Map<Class<?>, Converter> CONVERT_MAP = new HashMap<Class<?>, Converter>(50); private static final int[] ESCAPE_CHARS = new int[128]; static { FORMAT_MAP.put(boolean.class, PlainFormatter.INSTANCE); FORMAT_MAP.put(char.class, StringFormatter.INSTANCE); FORMAT_MAP.put(byte.class, ByteFormatter.INSTANCE); FORMAT_MAP.put(short.class, NumberFormatter.INSTANCE); FORMAT_MAP.put(int.class, NumberFormatter.INSTANCE); FORMAT_MAP.put(long.class, NumberFormatter.INSTANCE); FORMAT_MAP.put(float.class, FloatFormatter.INSTANCE); FORMAT_MAP.put(double.class, FloatFormatter.INSTANCE); FORMAT_MAP.put(boolean[].class, BooleanArrayFormatter.INSTANCE); FORMAT_MAP.put(char[].class, CharArrayFormatter.INSTANCE); FORMAT_MAP.put(byte[].class, ByteArrayFormatter.INSTANCE); FORMAT_MAP.put(short[].class, ShortArrayFormatter.INSTANCE); FORMAT_MAP.put(int[].class, IntArrayFormatter.INSTANCE); FORMAT_MAP.put(long[].class, LongArrayFormatter.INSTANCE); FORMAT_MAP.put(float[].class, FloatArrayFormatter.INSTANCE); FORMAT_MAP.put(double[].class, DoubleArrayFormatter.INSTANCE); FORMAT_MAP.put(Object[].class, ObjectArrayFormatter.INSTANCE); FORMAT_MAP.put(Boolean.class, PlainFormatter.INSTANCE); FORMAT_MAP.put(Character.class, StringFormatter.INSTANCE); FORMAT_MAP.put(Byte.class, ByteFormatter.INSTANCE); FORMAT_MAP.put(Short.class, NumberFormatter.INSTANCE); FORMAT_MAP.put(Integer.class, NumberFormatter.INSTANCE); FORMAT_MAP.put(Long.class, NumberFormatter.INSTANCE); FORMAT_MAP.put(Float.class, FloatFormatter.INSTANCE); FORMAT_MAP.put(Double.class, FloatFormatter.INSTANCE); FORMAT_MAP.put(BigInteger.class, NumberFormatter.INSTANCE); FORMAT_MAP.put(BigDecimal.class, NumberFormatter.INSTANCE); FORMAT_MAP.put(String.class, StringFormatter.INSTANCE); FORMAT_MAP.put(Date.class, DateFormatter.INSTANCE); FORMAT_MAP.put(java.sql.Date.class, DateFormatter.INSTANCE); FORMAT_MAP.put(java.sql.Time.class, DateFormatter.INSTANCE); FORMAT_MAP.put(java.sql.Timestamp.class, DateFormatter.INSTANCE); FORMAT_MAP.put(URI.class, StringFormatter.INSTANCE); FORMAT_MAP.put(URL.class, StringFormatter.INSTANCE); FORMAT_MAP.put(UUID.class, StringFormatter.INSTANCE); FORMAT_MAP.put(Pattern.class, StringFormatter.INSTANCE); FORMAT_MAP.put(Class.class, ClassFormatter.INSTANCE); FORMAT_MAP.put(Locale.class, LocaleFormatter.INSTANCE); FORMAT_MAP.put(ArrayList.class, ListFormatter.INSTANCE); FORMAT_MAP.put(LinkedList.class, IterableFormatter.INSTANCE); FORMAT_MAP.put(HashSet.class, IterableFormatter.INSTANCE); FORMAT_MAP.put(TreeSet.class, IterableFormatter.INSTANCE); FORMAT_MAP.put(LinkedHashSet.class, IterableFormatter.INSTANCE); FORMAT_MAP.put(HashMap.class, MapFormatter.INSTANCE); FORMAT_MAP.put(IdentityHashMap.class, MapFormatter.INSTANCE); FORMAT_MAP.put(Properties.class, MapFormatter.INSTANCE); FORMAT_MAP.put(TreeMap.class, MapFormatter.INSTANCE); FORMAT_MAP.put(LinkedHashMap.class, MapFormatter.INSTANCE); CONVERT_MAP.put(boolean.class, BooleanConverter.INSTANCE); CONVERT_MAP.put(char.class, CharacterConverter.INSTANCE); CONVERT_MAP.put(byte.class, ByteConverter.INSTANCE); CONVERT_MAP.put(short.class, ShortConverter.INSTANCE); CONVERT_MAP.put(int.class, IntegerConverter.INSTANCE); CONVERT_MAP.put(long.class, LongConverter.INSTANCE); CONVERT_MAP.put(float.class, FloatConverter.INSTANCE); CONVERT_MAP.put(double.class, DoubleConverter.INSTANCE); CONVERT_MAP.put(boolean[].class, ArrayConverter.INSTANCE); CONVERT_MAP.put(char[].class, ArrayConverter.INSTANCE); CONVERT_MAP.put(byte[].class, ArrayConverter.INSTANCE); CONVERT_MAP.put(short[].class, ArrayConverter.INSTANCE); CONVERT_MAP.put(int[].class, ArrayConverter.INSTANCE); CONVERT_MAP.put(long[].class, ArrayConverter.INSTANCE); CONVERT_MAP.put(float[].class, ArrayConverter.INSTANCE); CONVERT_MAP.put(double[].class, ArrayConverter.INSTANCE); CONVERT_MAP.put(Object[].class, ArrayConverter.INSTANCE); CONVERT_MAP.put(Boolean.class, BooleanConverter.INSTANCE); CONVERT_MAP.put(Character.class, CharacterConverter.INSTANCE); CONVERT_MAP.put(Byte.class, ByteConverter.INSTANCE); CONVERT_MAP.put(Short.class, ShortConverter.INSTANCE); CONVERT_MAP.put(Integer.class, IntegerConverter.INSTANCE); CONVERT_MAP.put(Long.class, LongConverter.INSTANCE); CONVERT_MAP.put(Float.class, FloatConverter.INSTANCE); CONVERT_MAP.put(Double.class, DoubleConverter.INSTANCE); CONVERT_MAP.put(BigInteger.class, BigIntegerConverter.INSTANCE); CONVERT_MAP.put(BigDecimal.class, BigDecimalConverter.INSTANCE); CONVERT_MAP.put(Number.class, BigDecimalConverter.INSTANCE); CONVERT_MAP.put(Pattern.class, PatternConverter.INSTANCE); CONVERT_MAP.put(TimeZone.class, TimeZoneConverter.INSTANCE); CONVERT_MAP.put(Locale.class, LocaleConverter.INSTANCE); CONVERT_MAP.put(File.class, FileConverter.INSTANCE); CONVERT_MAP.put(URL.class, URLConverter.INSTANCE); CONVERT_MAP.put(URI.class, URIConverter.INSTANCE); CONVERT_MAP.put(UUID.class, UUIDConverter.INSTANCE); CONVERT_MAP.put(Charset.class, CharsetConverter.INSTANCE); CONVERT_MAP.put(Class.class, ClassConverter.INSTANCE); CONVERT_MAP.put(Date.class, DateConverter.INSTANCE); CONVERT_MAP.put(java.sql.Date.class, DateConverter.INSTANCE); CONVERT_MAP.put(java.sql.Time.class, DateConverter.INSTANCE); CONVERT_MAP.put(java.sql.Timestamp.class, DateConverter.INSTANCE); CONVERT_MAP.put(Calendar.class, CalendarConverter.INSTANCE); CONVERT_MAP.put(Collection.class, CollectionConverter.INSTANCE); CONVERT_MAP.put(Set.class, CollectionConverter.INSTANCE); CONVERT_MAP.put(List.class, CollectionConverter.INSTANCE); CONVERT_MAP.put(SortedSet.class, CollectionConverter.INSTANCE); CONVERT_MAP.put(LinkedList.class, CollectionConverter.INSTANCE); CONVERT_MAP.put(HashSet.class, CollectionConverter.INSTANCE); CONVERT_MAP.put(TreeSet.class, CollectionConverter.INSTANCE); CONVERT_MAP.put(LinkedHashSet.class, CollectionConverter.INSTANCE); CONVERT_MAP.put(Map.class, MapConverter.INSTANCE); CONVERT_MAP.put(SortedMap.class, MapConverter.INSTANCE); CONVERT_MAP.put(HashMap.class, MapConverter.INSTANCE); CONVERT_MAP.put(IdentityHashMap.class, MapConverter.INSTANCE); CONVERT_MAP.put(TreeMap.class, MapConverter.INSTANCE); CONVERT_MAP.put(LinkedHashMap.class, MapConverter.INSTANCE); CONVERT_MAP.put(Properties.class, PropertiesConverter.INSTANCE); for (int i = 0; i < 32; i++) { ESCAPE_CHARS[i] = 1; } ESCAPE_CHARS['\''] = 2; ESCAPE_CHARS['"'] = 2; ESCAPE_CHARS['\\'] = 3; ESCAPE_CHARS[0x7F] = 1; } static JSON newInstance() { JSON instance = null; try { instance = prototype.newInstance(); } catch (Exception e) { throw new IllegalStateException(e); } return instance; } /** * Encodes a object into a json string. * * @param source a object to encode. * @return a json string * @throws JSONException if error occurred when formating. */ public static String encode(Object source) throws JSONException { return encode(source, false); } /** * Encodes a object into a json string. * * @param source a object to encode. * @param prettyPrint output a json string with indent, space or break. * @return a json string * @throws JSONException if error occurred when formating. */ public static String encode(Object source, boolean prettyPrint) throws JSONException { JSON json = newInstance(); json.setPrettyPrint(prettyPrint); return json.format(source); } /** * Encodes a object into a json string. * * @param source a object to encode. * @param out a destination to output a json string. * @throws IOException if I/O Error occurred. * @throws JSONException if error occurred when formating. */ public static void encode(Object source, OutputStream out) throws IOException, JSONException { newInstance().format(source, new OutputStreamWriter(out, "UTF-8")); } /** * Encodes a object into a json string. * * @param source a object to encode. * @param out a destination to output a json string. * @param prettyPrint output a json string with indent, space or break. * @throws IOException if I/O Error occurred. * @throws JSONException if error occurred when formating. */ public static void encode(Object source, OutputStream out, boolean prettyPrint) throws IOException, JSONException { JSON json = newInstance(); json.setPrettyPrint(prettyPrint); json.format(source, new OutputStreamWriter(out, "UTF-8")); } /** * Encodes a object into a json string. * * @param source a object to encode. * @param appendable a destination to output a json string. * @throws IOException if I/O Error occurred. * @throws JSONException if error occurred when formating. */ public static void encode(Object source, Appendable appendable) throws IOException, JSONException { newInstance().format(source, appendable); } /** * Encodes a object into a json string. * * @param source a object to encode. * @param appendable a destination to output a json string. * @param prettyPrint output a json string with indent, space or break. * @throws IOException if I/O Error occurred. * @throws JSONException if error occurred when formating. */ public static void encode(Object source, Appendable appendable, boolean prettyPrint) throws IOException, JSONException { JSON json = newInstance(); json.setPrettyPrint(prettyPrint); json.format(source, appendable); } /** * Escapes a object into JavaScript format. * * @param source a object to encode. * @return a escaped object * @throws JSONException if error occurred when formating. */ public static String escapeScript(Object source) throws JSONException { JSON json = newInstance(); json.setMode(Mode.SCRIPT); return json.format(source); } /** * Escapes a object into JavaScript format. * * @param source a object to encode. * @param out a destination to output a json string. * @throws IOException if I/O Error occurred. * @throws JSONException if error occurred when formating. */ public static void escapeScript(Object source, OutputStream out) throws IOException, JSONException { JSON json = newInstance(); json.setMode(Mode.SCRIPT); json.format(source, out); } /** * Escapes a object into JavaScript format. * * @param source a object to encode. * @param appendable a destination to output a json string. * @throws IOException if I/O Error occurred. * @throws JSONException if error occurred when formating. */ public static void escapeScript(Object source, Appendable appendable) throws IOException, JSONException { JSON json = newInstance(); json.setMode(Mode.SCRIPT); json.format(source, appendable); } /** * Decodes a json string into a object. * * @param source a json string to decode * @return a decoded object * @throws JSONException if error occurred when parsing. */ @SuppressWarnings("unchecked") public static <T> T decode(String source) throws JSONException { return (T) newInstance().parse(source); } /** * Decodes a json string into a typed object. * * @param source a json string to decode * @param cls class for converting * @return a decoded object * @throws JSONException if error occurred when parsing. */ public static <T> T decode(String source, Class<? extends T> cls) throws JSONException { return newInstance().parse(source, cls); } /** * Decodes a json string into a typed object. * * @param source a json string to decode * @param type type for converting * @return a decoded object * @throws JSONException if error occurred when parsing. */ @SuppressWarnings("unchecked") public static <T> T decode(String source, Type type) throws JSONException { return (T) newInstance().parse(source, type); } /** * Decodes a json stream into a object. (character encoding should be Unicode) * * @param in a json stream to decode * @return a decoded object * @throws IOException if I/O error occurred. * @throws JSONException if error occurred when parsing. */ @SuppressWarnings("unchecked") public static <T> T decode(InputStream in) throws IOException, JSONException { return (T) newInstance().parse(in); } /** * Decodes a json stream into a object. (character encoding should be Unicode) * * @param in a json stream to decode * @param cls class for converting * @return a decoded object * @throws IOException if I/O error occurred. * @throws JSONException if error occurred when parsing. */ public static <T> T decode(InputStream in, Class<? extends T> cls) throws IOException, JSONException { return newInstance().parse(in, cls); } /** * Decodes a json stream into a object. (character encoding should be Unicode) * * @param in a json stream to decode * @param type type for converting * @return a decoded object * @throws IOException if I/O error occurred. * @throws JSONException if error occurred when parsing. */ @SuppressWarnings("unchecked") public static <T> T decode(InputStream in, Type type) throws IOException, JSONException { return (T) newInstance().parse(in, type); } /** * Decodes a json stream into a object. * * @param reader a json stream to decode * @return a decoded object * @throws IOException if I/O error occurred. * @throws JSONException if error occurred when parsing. */ @SuppressWarnings("unchecked") public static <T> T decode(Reader reader) throws IOException, JSONException { return (T) newInstance().parse(reader); } /** * Decodes a json stream into a object. * * @param reader a json stream to decode * @param cls class for converting * @return a decoded object * @throws IOException if I/O error occurred. * @throws JSONException if error occurred when parsing. */ public static <T> T decode(Reader reader, Class<? extends T> cls) throws IOException, JSONException { return newInstance().parse(reader, cls); } /** * Decodes a json stream into a object. * * @param reader a json stream to decode * @param type type for converting * @return a decoded object * @throws IOException if I/O error occurred. * @throws JSONException if error occurred when parsing. */ @SuppressWarnings("unchecked") public static <T> T decode(Reader reader, Type type) throws IOException, JSONException { return (T) newInstance().parse(reader, type); } /** * Validates a json text * * @param cs source a json string to decode * @throws JSONException if error occurred when parsing. */ public static void validate(CharSequence cs) throws JSONException { JSON json = newInstance(); json.setMode(Mode.STRICT); json.setMaxDepth(0); json.parse(cs); } /** * Validates a json stream * * @param in source a json string to decode * @throws IOException if I/O error occurred. * @throws JSONException if error occurred when parsing. */ public static void validate(InputStream in) throws IOException, JSONException { JSON json = newInstance(); json.setMode(Mode.STRICT); json.setMaxDepth(0); json.parse(in); } /** * Validates a json stream * * @param reader source a json string to decode * @throws IOException if I/O error occurred. * @throws JSONException if error occurred when parsing. */ public static void validate(Reader reader) throws IOException, JSONException { JSON json = newInstance(); json.setMode(Mode.STRICT); json.setMaxDepth(0); json.parse(reader); } private Object contextObject; private Locale locale = Locale.getDefault(); private TimeZone timeZone = TimeZone.getDefault(); private boolean prettyPrint = false; private int maxDepth = 32; private boolean suppressNull = false; private Mode mode = Mode.TRADITIONAL; private String dateFormat; private String numberFormat; private NamingStyle propertyStyle; private NamingStyle enumStyle; public JSON() { } public JSON(int maxDepth) { setMaxDepth(maxDepth); } public JSON(Mode mode) { setMode(mode); } /** * Sets context for inner class. * * @param value context object */ public void setContext(Object value) { this.contextObject = value; } /** * Sets locale for formatting, converting and selecting message. * * @param locale locale for formatting, converting and selecting message */ public void setLocale(Locale locale) { if (locale == null) { throw new NullPointerException(); } this.locale = locale; } /** * Sets timeZone for formatting and converting. * * @param timeZone timeZone for formatting and converting. */ public void setTimeZone(TimeZone timeZone) { if (timeZone == null) { throw new NullPointerException(); } this.timeZone = timeZone; } /** * Output json string is to human-readable format. * * @param value true to format human-readable, false to shorten. */ public void setPrettyPrint(boolean value) { this.prettyPrint = value; } /** * Sets maximum depth for the nest depth. * default value is 32. * * @param value maximum depth for the nest depth. */ public void setMaxDepth(int value) { if (value < 0) { throw new IllegalArgumentException(getMessage("json.TooSmallArgumentError", "maxDepth", 0)); } this.maxDepth = value; } /** * Gets maximum depth for the nest depth. * * @return a maximum depth */ public int getMaxDepth() { return this.maxDepth; } /** * If this property is true, the member of null value in JSON object is ignored. * default value is false. * * @param value true to ignore the member of null value in JSON object. */ public void setSuppressNull(boolean value) { this.suppressNull = value; } /** * Sets JSON interpreter mode. * * @param mode JSON interpreter mode */ public void setMode(Mode mode) { if (mode == null) { throw new NullPointerException(); } this.mode = mode; } /** * Gets JSON interpreter mode. * * @return JSON interpreter mode */ public Mode getMode() { return mode; } /** * Sets default Date format. * When format is null, Date is formated to JSON number. * * @param format default Date format */ public void setDateFormat(String format) { this.dateFormat = format; } /** * Sets default Number format. * When format is null, Number is formated to JSON number. * * @param format default Number format */ public void setNumberFormat(String format) { this.numberFormat = format; } /** * Sets default Case style for the property name of JSON object. * * @param style default Case style for keys of JSON object. */ public void setPropertyStyle(NamingStyle style) { this.propertyStyle = style; } /** * Sets default Case style for Enum. * * @param style default Case style for Enum. */ public void setEnumStyle(NamingStyle style) { this.enumStyle = style; } /** * Format a object into a json string. * * @param source a object to encode. * @return a json string */ public String format(Object source) { String text = null; try { text = format(source, new StringBuilder(1000)).toString(); } catch (IOException e) { // no handle; } return text; } /** * Format a object into a json string. * * @param source a object to encode. * @param out a destination to output a json string. * @return a reference to 'out' object in parameters */ public OutputStream format(Object source, OutputStream out) throws IOException { format(source, new BufferedWriter(new OutputStreamWriter(out, "UTF-8"))); return out; } /** * Format a object into a json string. * * @param source a object to encode. * @param ap a destination. example: StringBuilder, Writer, ... * @return a json string */ public Appendable format(Object source, Appendable ap) throws IOException { Context context = new Context(); OutputSource fs; if (ap instanceof Writer) { fs = new WriterOutputSource((Writer) ap); } else if (ap instanceof StringBuilder) { fs = new StringBuilderOutputSource((StringBuilder) ap); } else { fs = new AppendableOutputSource(ap); } context.enter('$'); source = preformatInternal(context, source); formatInternal(context, source, fs); context.exit(); fs.flush(); return ap; } /** * Converts Any Java Object to JSON recognizable Java object before format. * * @param context current context. * @param value source a object to format. * @return null or the instance of Map, Iterator(or Array, Enumerator), Number, CharSequence or Boolean. * @throws Exception if conversion failed. */ protected Object preformat(Context context, Object value) throws Exception { return value; } final Object preformatInternal(Context context, Object value) { if (value == null) { return null; } else if (context.getDepth() > context.getMaxDepth()) { return null; } else if (getClass() != JSON.class) { try { return preformat(context, value); } catch (Exception e) { throw new JSONException(getMessage("json.format.ConversionError", value, context), JSONException.PREFORMAT_ERROR, e); } } return value; } final Formatter formatInternal(final Context context, final Object src, final OutputSource ap) throws IOException { Object o = src; Formatter f = null; if (o == null) { f = NullFormatter.INSTANCE; } else { JSONHint hint = context.getHint(); if (hint == null) { // no handle } else if (hint.serialized() && hint != context.skipHint) { f = PlainFormatter.INSTANCE; } else if (String.class.equals(hint.type())) { f = StringFormatter.INSTANCE; } else if (Serializable.class.equals(hint.type())) { f = SerializableFormatter.INSTANCE; } } if (f == null) f = FORMAT_MAP.get(o.getClass()); if (f == null) { if (context.hasMemberCache(o.getClass())) { f = ObjectFormatter.INSTANCE; } else if (o instanceof Map<?, ?>) { f = MapFormatter.INSTANCE; } else if (o instanceof Iterable<?>) { if (o instanceof RandomAccess && o instanceof List<?>) { f = ListFormatter.INSTANCE; } else { f = IterableFormatter.INSTANCE; } } else if (o instanceof Object[]) { f = ObjectArrayFormatter.INSTANCE; } else if (o instanceof Enum<?>) { f = EnumFormatter.INSTANCE; } else if (o instanceof CharSequence) { f = StringFormatter.INSTANCE; } else if (o instanceof Date) { f = DateFormatter.INSTANCE; } else if (o instanceof Calendar) { f = CalendarFormatter.INSTANCE; } else if (o instanceof Number) { f = NumberFormatter.INSTANCE; } else if (o instanceof Iterator<?>) { f = IteratorFormatter.INSTANCE; } else if (o instanceof Enumeration) { f = EnumerationFormatter.INSTANCE; } else if (o instanceof Type || o instanceof Member || o instanceof File) { f = StringFormatter.INSTANCE; } else if (o instanceof TimeZone) { f = TimeZoneFormatter.INSTANCE; } else if (o instanceof Charset) { f = CharsetFormatter.INSTANCE; } else if (o instanceof java.sql.Array) { f = SQLArrayFormatter.INSTANCE; } else if (o instanceof Struct) { f = StructFormmatter.INSTANCE; } else if (o instanceof Node) { if (o instanceof CharacterData && !(o instanceof Comment)) { f = CharacterDataFormatter.INSTANCE; } else if (o instanceof Document) { f = DOMDocumentFormatter.INSTANCE; } else if (o instanceof Element) { f = DOMElementFormatter.INSTANCE; } } else if (isAssignableFrom(ClassUtil.findClass("java.sql.RowId"), o.getClass())) { f = SerializableFormatter.INSTANCE; } else if (isAssignableFrom(ClassUtil.findClass("java.net.InetAddress"), o.getClass())) { f = InetAddressFormatter.INSTANCE; } else if (isAssignableFrom(ClassUtil.findClass("org.apache.commons.beanutils.DynaBean"), o.getClass())) { f = DynaBeanFormatter.INSTANCE; } else { f = ObjectFormatter.INSTANCE; } } boolean isStruct; try { isStruct = f.format(this, context, src, o, ap); } catch (IOException e) { throw e; } catch (Exception e) { throw new JSONException( getMessage("json.format.ConversionError", (src instanceof CharSequence) ? "\"" + src + "\"" : src, context), JSONException.FORMAT_ERROR, e); } if (!isStruct && context.getDepth() == 0 && context.getMode() != Mode.SCRIPT) { throw new JSONException(getMessage("json.format.IllegalRootTypeError"), JSONException.FORMAT_ERROR); } return f; } @SuppressWarnings("unchecked") public <T> T parse(CharSequence cs) throws JSONException { Object value = null; try { value = parseInternal(new Context(), new CharSequenceInputSource(cs)); } catch (IOException e) { // never occur } return (T) value; } @SuppressWarnings("unchecked") public <T> T parse(CharSequence s, Class<? extends T> cls) throws JSONException { return (T) parse(s, (Type) cls); } @SuppressWarnings("unchecked") public <T> T parse(CharSequence s, Type type) throws JSONException { T value = null; try { Context context = new Context(); value = (T) convert(context, parseInternal(context, new CharSequenceInputSource(s)), type); } catch (IOException e) { // never occur } return value; } @SuppressWarnings("unchecked") public <T> T parse(InputStream in) throws IOException, JSONException { return (T) parseInternal(new Context(), new ReaderInputSource(in)); } @SuppressWarnings("unchecked") public <T> T parse(InputStream in, Class<? extends T> cls) throws IOException, JSONException { return (T) parse(in, (Type) cls); } @SuppressWarnings("unchecked") public <T> T parse(InputStream in, Type type) throws IOException, JSONException { Context context = new Context(); return (T) convert(context, parseInternal(context, new ReaderInputSource(in)), type); } @SuppressWarnings("unchecked") public <T> T parse(Reader reader) throws IOException, JSONException { return (T) parseInternal(new Context(), new ReaderInputSource(reader)); } @SuppressWarnings("unchecked") public <T> T parse(Reader reader, Class<? extends T> cls) throws IOException, JSONException { return (T) parse(reader, (Type) cls); } @SuppressWarnings("unchecked") public <T> T parse(Reader reader, Type type) throws IOException, JSONException { Context context = new Context(); return (T) convert(context, parseInternal(context, new ReaderInputSource(reader)), type); } private Object parseInternal(Context context, InputSource s) throws IOException, JSONException { boolean isEmpty = true; Object o = null; int n = -1; while ((n = s.next()) != -1) { char c = (char) n; switch (c) { case '\r': case '\n': case ' ': case '\t': case 0xFEFF: // BOM continue; case '{': if (isEmpty) { s.back(); o = parseObject(context, s, 1); isEmpty = false; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case '[': if (isEmpty) { s.back(); o = parseArray(context, s, 1); isEmpty = false; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case '/': case '#': if (context.getMode() == Mode.TRADITIONAL || (context.getMode() == Mode.SCRIPT && c == '/')) { s.back(); skipComment(context, s); continue; } case '\'': case '"': if (context.getMode() == Mode.SCRIPT) { if (isEmpty) { s.back(); o = parseString(context, s, 1); isEmpty = false; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; } default: if (context.getMode() == Mode.SCRIPT) { if (isEmpty) { s.back(); o = ((c == '-') || (c >= '0' && c <= '9')) ? parseNumber(context, s, 1) : parseLiteral(context, s, 1, false); isEmpty = false; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; } } if (context.getMode() == Mode.TRADITIONAL && isEmpty) { s.back(); o = parseObject(context, s, 1); isEmpty = false; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } } if (isEmpty) { if (context.getMode() == Mode.TRADITIONAL) { o = new LinkedHashMap<String, Object>(); } else { throw createParseException(getMessage("json.parse.EmptyInputError"), s); } } return o; } private Map<Object, Object> parseObject(Context context, InputSource s, int level) throws IOException, JSONException { int point = 0; // 0 '{' 1 'key' 2 ':' 3 '\n'? 4 'value' 5 '\n'? 6 ',' ... '}' E Map<Object, Object> map = (level <= context.getMaxDepth()) ? new LinkedHashMap<Object, Object>() : null; Object key = null; char start = '\0'; int n = -1; loop: while ((n = s.next()) != -1) { char c = (char) n; switch (c) { case '\r': case '\n': if (context.getMode() == Mode.TRADITIONAL && point == 5) { point = 6; } continue; case ' ': case '\t': case 0xFEFF: // BOM continue; case '{': if (point == 0) { start = '{'; point = 1; } else if (point == 2 || point == 3) { s.back(); Object value = parseObject(context, s, level + 1); if (level < context.getMaxDepth()) map.put(key, value); point = 5; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case ':': if (point == 2) { point = 3; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case ',': if (point == 5 || point == 6 || (context.getMode() == Mode.TRADITIONAL && point == 3)) { if (point == 3 && level < context.getMaxDepth() && !context.isSuppressNull()) { map.put(key, null); } point = 1; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case '}': if (start == '{' && (point == 1 || point == 5 || point == 6 || (context.getMode() == Mode.TRADITIONAL && point == 3))) { if (point == 3 && level < context.getMaxDepth() && !context.isSuppressNull()) { map.put(key, null); } } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break loop; case '[': if (point == 3) { s.back(); List<Object> value = parseArray(context, s, level + 1); if (level < context.getMaxDepth()) map.put(key, value); point = 5; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case '\'': if (context.getMode() == Mode.STRICT) { break; } case '"': if (point == 0) { s.back(); point = 1; } else if (point == 1 || point == 6) { s.back(); key = parseString(context, s, level + 1); point = 2; } else if (point == 3) { s.back(); String value = parseString(context, s, level + 1); if (level < context.getMaxDepth()) map.put(key, value); point = 5; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case '/': case '#': if (context.getMode() == Mode.TRADITIONAL || (context.getMode() == Mode.SCRIPT && c == '/')) { s.back(); skipComment(context, s); if (point == 5) { point = 6; } continue; } } if (point == 0) { s.back(); point = 1; } else if (point == 1 || point == 6) { s.back(); key = ((c == '-') || (c >= '0' && c <= '9')) ? parseNumber(context, s, level + 1) : parseLiteral(context, s, level + 1, context.getMode() != Mode.STRICT); point = 2; } else if (point == 3) { s.back(); Object value = ((c == '-') || (c >= '0' && c <= '9')) ? parseNumber(context, s, level + 1) : parseLiteral(context, s, level + 1, context.getMode() == Mode.TRADITIONAL); if (level < context.getMaxDepth() && (value != null || !context.isSuppressNull())) { map.put(key, value); } point = 5; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } } if (n == -1) { if (point == 3 || point == 4) { if (level < context.getMaxDepth() && !context.isSuppressNull()) map.put(key, null); } else if (point == 2) { throw createParseException(getMessage("json.parse.ObjectNotClosedError"), s); } } if ((n == -1) ? (start != '\0') : (n != '}')) { throw createParseException(getMessage("json.parse.ObjectNotClosedError"), s); } return map; } private List<Object> parseArray(Context context, InputSource s, int level) throws IOException, JSONException { int point = 0; // 0 '[' 1 'value' 2 '\n'? 3 ',' 4 ... ']' E List<Object> list = (level <= context.getMaxDepth()) ? new ArrayList<Object>() : null; int n = -1; loop: while ((n = s.next()) != -1) { char c = (char) n; switch (c) { case '\r': case '\n': if (context.getMode() == Mode.TRADITIONAL && point == 2) { point = 3; } continue; case ' ': case '\t': case 0xFEFF: // BOM continue; case '[': if (point == 0) { point = 1; } else if (point == 1 || point == 3 || point == 4) { s.back(); List<Object> value = parseArray(context, s, level + 1); if (level < context.getMaxDepth()) list.add(value); point = 2; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case ',': if (context.getMode() == Mode.TRADITIONAL && (point == 1 || point == 4)) { if (level < context.getMaxDepth()) list.add(null); } else if (point == 2 || point == 3) { point = 4; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case ']': if (point == 1 || point == 2 || point == 3) { // nothing } else if (context.getMode() == Mode.TRADITIONAL && point == 4) { if (level < context.getMaxDepth()) list.add(null); } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break loop; case '{': if (point == 1 || point == 3 || point == 4) { s.back(); Map<Object, Object> value = parseObject(context, s, level + 1); if (level < context.getMaxDepth()) list.add(value); point = 2; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case '\'': if (context.getMode() == Mode.STRICT) { break; } case '"': if (point == 1 || point == 3 || point == 4) { s.back(); String value = parseString(context, s, level + 1); if (level < context.getMaxDepth()) list.add(value); point = 2; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } continue; case '/': case '#': if (context.getMode() == Mode.TRADITIONAL || (context.getMode() == Mode.SCRIPT && c == '/')) { s.back(); skipComment(context, s); if (point == 2) { point = 3; } continue; } } if (point == 1 || point == 3 || point == 4) { s.back(); Object value = ((c == '-') || (c >= '0' && c <= '9')) ? parseNumber(context, s, level + 1) : parseLiteral(context, s, level + 1, context.getMode() == Mode.TRADITIONAL); if (level < context.getMaxDepth()) list.add(value); point = 2; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } } if (n != ']') { throw createParseException(getMessage("json.parse.ArrayNotClosedError"), s); } return list; } private String parseString(Context context, InputSource s, int level) throws IOException, JSONException { StringBuilder sb = (level <= context.getMaxDepth()) ? context.getCachedBuffer() : null; char start = '\0'; int n = -1; loop: while ((n = s.next()) != -1) { char c = (char) n; if (start == '\0') { switch (c) { case '\'': if (context.getMode() == Mode.STRICT) { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } case '"': start = c; break; default: throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } } else if (c < ESCAPE_CHARS.length) { switch (ESCAPE_CHARS[c]) { case 0: if (sb != null) sb.append(c); break; case 1: // control chars if (context.getMode() != Mode.STRICT) { if (sb != null) sb.append(c); } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break; case 2: // "' if (start == c) { break loop; } else { if (sb != null) sb.append(c); } break; case 3: // escape chars if (context.getMode() != Mode.TRADITIONAL || start == '"') { s.back(); c = parseEscape(s); } if (sb != null) sb.append(c); break; } } else if (c != 0xFEFF) { if (sb != null) sb.append(c); } } if (n != start) { throw createParseException(getMessage("json.parse.StringNotClosedError"), s); } return (sb != null) ? sb.toString() : null; } private Object parseLiteral(Context context, InputSource s, int level, boolean any) throws IOException, JSONException { int point = 0; // 0 'IdStart' 1 'IdPart' ... !'IdPart' E StringBuilder sb = context.getCachedBuffer(); int n = -1; loop: while ((n = s.next()) != -1) { char c = (char) n; if (c == 0xFEFF) continue; if (c == '\\') { s.back(); c = parseEscape(s); } if (point == 0 && Character.isJavaIdentifierStart(c)) { sb.append(c); point = 1; } else if (point == 1 && (Character.isJavaIdentifierPart(c) || c == '.')) { sb.append(c); } else { s.back(); break loop; } } String str = sb.toString(); if ("null".equals(str)) return null; if ("true".equals(str)) return true; if ("false".equals(str)) return false; if (!any) { throw createParseException(getMessage("json.parse.UnrecognizedLiteral", str), s); } return str; } private Number parseNumber(Context context, InputSource s, int level) throws IOException, JSONException { int point = 0; // 0 '(-)' 1 '0' | ('[1-9]' 2 '[0-9]*') 3 '(.)' 4 '[0-9]' 5 '[0-9]*' 6 'e|E' 7 '[+|-]' 8 '[0-9]' 9 '[0-9]*' E StringBuilder sb = (level <= context.getMaxDepth()) ? context.getCachedBuffer() : null; int n = -1; loop: while ((n = s.next()) != -1) { char c = (char) n; switch (c) { case 0xFEFF: // BOM break; case '+': if (point == 7) { if (sb != null) sb.append(c); point = 8; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break; case '-': if (point == 0) { if (sb != null) sb.append(c); point = 1; } else if (point == 7) { if (sb != null) sb.append(c); point = 8; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break; case '.': if (point == 2 || point == 3) { if (sb != null) sb.append(c); point = 4; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break; case 'e': case 'E': if (point == 2 || point == 3 || point == 5 || point == 6) { if (sb != null) sb.append(c); point = 7; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break; default: if (c >= '0' && c <= '9') { if (point == 0 || point == 1) { if (sb != null) sb.append(c); point = (c == '0') ? 3 : 2; } else if (point == 2 || point == 5 || point == 9) { if (sb != null) sb.append(c); } else if (point == 4) { if (sb != null) sb.append(c); point = 5; } else if (point == 7 || point == 8) { if (sb != null) sb.append(c); point = 9; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } } else if (point == 2 || point == 3 || point == 5 || point == 6 || point == 9) { s.back(); break loop; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } } } return (sb != null) ? new BigDecimal(sb.toString()) : null; } private char parseEscape(InputSource s) throws IOException, JSONException { int point = 0; // 0 '\' 1 'u' 2 'x' 3 'x' 4 'x' 5 'x' E char escape = '\0'; int n = -1; loop: while ((n = s.next()) != -1) { char c = (char) n; if (c == 0xFEFF) continue; // BOM if (point == 0) { if (c == '\\') { point = 1; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } } else if (point == 1) { switch (c) { case 'b': escape = '\b'; break loop; case 'f': escape = '\f'; break loop; case 'n': escape = '\n'; break loop; case 'r': escape = '\r'; break loop; case 't': escape = '\t'; break loop; case 'u': point = 2; break; default: escape = c; break loop; } } else { int hex = (c >= '0' && c <= '9') ? c - 48 : (c >= 'A' && c <= 'F') ? c - 65 + 10 : (c >= 'a' && c <= 'f') ? c - 97 + 10 : -1; if (hex != -1) { escape |= (hex << ((5 - point) * 4)); if (point != 5) { point++; } else { break loop; } } else { throw createParseException(getMessage("json.parse.IllegalUnicodeEscape", c), s); } } } return escape; } private void skipComment(Context context, InputSource s) throws IOException, JSONException { int point = 0; // 0 '/' 1 '*' 2 '*' 3 '/' E or 0 '/' 1 '/' 4 '\r|\n|\r\n' E int n = -1; loop: while ((n = s.next()) != -1) { char c = (char) n; switch (c) { case 0xFEFF: break; case '/': if (point == 0) { point = 1; } else if (point == 1) { point = 4; } else if (point == 3) { break loop; } else if (!(point == 2 || point == 4)) { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break; case '*': if (point == 1) { point = 2; } else if (point == 2) { point = 3; } else if (!(point == 3 || point == 4)) { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break; case '\n': case '\r': if (point == 2 || point == 3) { point = 2; } else if (point == 4) { break loop; } else { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break; case '#': if (context.getMode() == Mode.TRADITIONAL) { if (point == 0) { point = 4; } else if (point == 3) { point = 2; } else if (!(point == 2 || point == 4)) { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } break; } default: if (point == 3) { point = 2; } else if (!(point == 2 || point == 4)) { throw createParseException(getMessage("json.parse.UnexpectedChar", c), s); } } } } JSONException createParseException(String message, InputSource s) { return new JSONException("" + s.getLineNumber() + ": " + message + "\n" + s.toString() + " <- ?", JSONException.PARSE_ERROR, s.getLineNumber(), s.getColumnNumber(), s.getOffset()); } String getMessage(String id, Object... args) { ResourceBundle bundle = ResourceBundle.getBundle("net.arnx.jsonic.Messages", locale); return MessageFormat.format(bundle.getString(id), args); } public Object convert(Object value, Type type) throws JSONException { return convert(new Context(), value, type); } @SuppressWarnings("unchecked") private <T> T convert(Context context, Object value, Type type) throws JSONException { Class<?> cls = ClassUtil.getRawType(type); T result = null; try { context.enter('$'); result = (T) postparse(context, value, cls, type); context.exit(); } catch (Exception e) { String text; if (value instanceof CharSequence) { text = "\"" + value + "\""; } else { try { text = value.toString(); } catch (Exception e2) { text = value.getClass().toString(); } } throw new JSONException(getMessage("json.parse.ConversionError", text, type, context), JSONException.POSTPARSE_ERROR, e); } return result; } /** * Converts Map, List, Number, String, Boolean or null to other Java Objects after parsing. * * @param context current context. * @param value null or the instance of Map, List, Number, String or Boolean. * @param cls class for converting * @param type generics type for converting. type equals to c if not generics. * @return a converted object * @throws Exception if conversion failed. */ @SuppressWarnings("unchecked") protected <T> T postparse(Context context, Object value, Class<? extends T> cls, Type type) throws Exception { Converter c = null; if (value == null) { if (!cls.isPrimitive()) { c = NullConverter.INSTANCE; } } else { JSONHint hint = context.getHint(); if (hint == null) { // no handle } else if (hint.serialized() && hint != context.skipHint) { c = FormatConverter.INSTANCE; } else if (Serializable.class.equals(hint.type())) { c = SerializableConverter.INSTANCE; } else if (String.class.equals(hint.type())) { c = StringSerializableConverter.INSTANCE; } } if (c == null) { if (value != null && cls.equals(type) && cls.isAssignableFrom(value.getClass())) { c = PlainConverter.INSTANCE; } else { c = CONVERT_MAP.get(cls); } } if (c == null) { if (context.hasMemberCache(cls)) { c = ObjectConverter.INSTANCE; } else if (Properties.class.isAssignableFrom(cls)) { c = PropertiesConverter.INSTANCE; } else if (Map.class.isAssignableFrom(cls)) { c = MapConverter.INSTANCE; } else if (Collection.class.isAssignableFrom(cls)) { c = CollectionConverter.INSTANCE; } else if (cls.isArray()) { c = ArrayConverter.INSTANCE; } else if (cls.isEnum()) { c = EnumConverter.INSTANCE; } else if (Date.class.isAssignableFrom(cls)) { c = DateConverter.INSTANCE; } else if (Calendar.class.isAssignableFrom(cls)) { c = CalendarConverter.INSTANCE; } else if (CharSequence.class.isAssignableFrom(cls)) { c = CharSequenceConverter.INSTANCE; } else if (Appendable.class.isAssignableFrom(cls)) { c = AppendableConverter.INSTANCE; } else if (cls.equals(ClassUtil.findClass("java.net.InetAddress"))) { c = InetAddressConverter.INSTANCE; } else if (java.sql.Array.class.isAssignableFrom(cls) || Struct.class.isAssignableFrom(cls)) { c = NullConverter.INSTANCE; } else { c = ObjectConverter.INSTANCE; } } if (c != null) { return (T) c.convert(this, context, value, cls, type); } else { throw new UnsupportedOperationException(); } } protected String normalize(String name) { return name; } /** * Ignore this property. A default behavior is to ignore transient or declaring method in java.lang.Object. * You can override this method if you have to change default behavior. * * @param context current context * @param target target class * @param member target member * @return true if this member must be ignored. */ protected boolean ignore(Context context, Class<?> target, Member member) { if (Modifier.isTransient(member.getModifiers())) return true; if (member.getDeclaringClass().equals(Object.class)) return true; return false; } protected <T> T create(Context context, Class<? extends T> c) throws Exception { Object instance = null; JSONHint hint = context.getHint(); if (hint != null && hint.type() != Object.class) c = hint.type().asSubclass(c); if (c.isInterface()) { if (SortedMap.class.equals(c)) { instance = new TreeMap<Object, Object>(); } else if (Map.class.equals(c)) { instance = new LinkedHashMap<Object, Object>(); } else if (SortedSet.class.equals(c)) { instance = new TreeSet<Object>(); } else if (Set.class.equals(c)) { instance = new LinkedHashSet<Object>(); } else if (List.class.equals(c)) { instance = new ArrayList<Object>(); } else if (Collection.class.equals(c)) { instance = new ArrayList<Object>(); } else if (Appendable.class.equals(c)) { instance = new StringBuilder(); } } else if (Modifier.isAbstract(c.getModifiers())) { if (Calendar.class.equals(c)) { instance = Calendar.getInstance(); } } else if ((c.isMemberClass() || c.isAnonymousClass()) && !Modifier.isStatic(c.getModifiers())) { Class<?> eClass = c.getEnclosingClass(); Constructor<?> con = c.getDeclaredConstructor(eClass); con.setAccessible(true); if (context.contextObject != null && eClass.isAssignableFrom(context.contextObject.getClass())) { instance = con.newInstance(context.contextObject); } else { instance = con.newInstance((Object) null); } } else { if (Date.class.isAssignableFrom(c)) { try { Constructor<?> con = c.getDeclaredConstructor(long.class); con.setAccessible(true); instance = con.newInstance(0l); } catch (NoSuchMethodException e) { // no handle } } if (instance == null) { Constructor<?> con = c.getDeclaredConstructor(); con.setAccessible(true); instance = con.newInstance(); } } return c.cast(instance); } private static boolean isAssignableFrom(Class<?> target, Class<?> cls) { return (target != null) && target.isAssignableFrom(cls); } public final class Context { private final Locale locale; private final TimeZone timeZone; private final Object contextObject; private final int maxDepth; private final boolean prettyPrint; private final boolean suppressNull; private final Mode mode; private final String numberFormat; private final String dateFormat; private final NamingStyle propertyStyle; private final NamingStyle enumStyle; private Object[] path; private int depth = -1; private Map<Class<?>, Object> memberCache; private Map<String, DateFormat> dateFormatCache; private Map<String, NumberFormat> numberFormatCache; private StringBuilder builderCache; JSONHint skipHint; public Context() { synchronized (JSON.this) { locale = JSON.this.locale; timeZone = JSON.this.timeZone; contextObject = JSON.this.contextObject; maxDepth = JSON.this.maxDepth; prettyPrint = JSON.this.prettyPrint; suppressNull = JSON.this.suppressNull; mode = JSON.this.mode; numberFormat = JSON.this.numberFormat; dateFormat = JSON.this.dateFormat; propertyStyle = JSON.this.propertyStyle; enumStyle = JSON.this.enumStyle; } } Context(Context context) { synchronized (context) { locale = context.locale; timeZone = context.timeZone; contextObject = context.contextObject; maxDepth = context.maxDepth; prettyPrint = context.prettyPrint; suppressNull = context.suppressNull; mode = context.mode; numberFormat = context.numberFormat; dateFormat = context.dateFormat; propertyStyle = context.propertyStyle; enumStyle = context.enumStyle; depth = context.depth; path = context.path.clone(); } } public StringBuilder getCachedBuffer() { if (builderCache == null) { builderCache = new StringBuilder(); } else { builderCache.setLength(0); } return builderCache; } public Locale getLocale() { return locale; } public TimeZone getTimeZone() { return timeZone; } public int getMaxDepth() { return maxDepth; } public boolean isPrettyPrint() { return prettyPrint; } public boolean isSuppressNull() { return suppressNull; } public Mode getMode() { return mode; } public NamingStyle getPropertyStyle() { return propertyStyle; } public NamingStyle getEnumStyle() { return enumStyle; } @Deprecated public NamingStyle getPropertyCaseStyle() { return propertyStyle; } @Deprecated public NamingStyle getEnumCaseStyle() { return enumStyle; } /** * Returns the current level. * * @return level number. 0 is root node. */ @Deprecated public int getLevel() { return depth; } /** * Returns the current depth. * * @return depth number. 0 is root node. */ public int getDepth() { return depth; } /** * Returns the current key object. * * @return Root node is '$'. When the parent is a array, the key is Integer, otherwise String. */ public Object getKey() { return path[depth * 2]; } /** * Returns the key object in any depth. the negative value means relative to current depth. * * @return Root node is '$'. When the parent is a array, the key is Integer, otherwise String. */ public Object getKey(int level) { if (level < 0) level = getDepth() + level; return path[level * 2]; } /** * Returns the current hint annotation. * * @return the current annotation if present on this context, else null. */ public JSONHint getHint() { return (JSONHint) path[depth * 2 + 1]; } @SuppressWarnings("unchecked") public <T> T convert(Object key, Object value, Class<? extends T> c) throws Exception { enter(key); T o = JSON.this.postparse(this, value, c, c); exit(); return (T) ((c.isPrimitive()) ? PlainConverter.getDefaultValue(c).getClass() : c).cast(o); } public Object convert(Object key, Object value, Type t) throws Exception { Class<?> c = ClassUtil.getRawType(t); enter(key); Object o = JSON.this.postparse(this, value, c, t); exit(); return ((c.isPrimitive()) ? PlainConverter.getDefaultValue(c).getClass() : c).cast(o); } void enter(Object key, JSONHint hint) { depth++; if (path == null) path = new Object[8]; if (path.length < depth * 2 + 2) { Object[] newPath = new Object[Math.max(path.length * 2, depth * 2 + 2)]; System.arraycopy(path, 0, newPath, 0, path.length); path = newPath; } path[depth * 2] = key; path[depth * 2 + 1] = hint; } void enter(Object key) { enter(key, (JSONHint) ((depth != -1) ? path[depth * 2 + 1] : null)); } void exit() { depth--; } boolean hasMemberCache(Class<?> c) { return memberCache != null && memberCache.containsKey(c); } @SuppressWarnings("unchecked") List<PropertyInfo> getGetProperties(Class<?> c) { if (memberCache == null) memberCache = new HashMap<Class<?>, Object>(); List<PropertyInfo> props = (List<PropertyInfo>) memberCache.get(c); if (props == null) { Map<String, PropertyInfo> map = new HashMap<String, PropertyInfo>(); // Field for (PropertyInfo prop : BeanInfo.get(c).getProperties()) { Field f = prop.getField(); if (f == null || ignore(this, c, f)) continue; JSONHint hint = f.getAnnotation(JSONHint.class); String name = null; int ordinal = prop.getOrdinal(); if (hint != null) { if (hint.ignore()) continue; ordinal = hint.ordinal(); if (hint.name().length() != 0) name = hint.name(); } if (name == null) { name = normalize(prop.getName()); if (getPropertyStyle() != null) { name = getPropertyStyle().to(name); } } if (!name.equals(prop.getName()) || ordinal != prop.getOrdinal() || f != prop.getReadMember()) { map.put(name, new PropertyInfo(prop.getBeanClass(), name, prop.getField(), null, null, prop.isStatic(), ordinal)); } else { map.put(name, prop); } } // Method for (PropertyInfo prop : BeanInfo.get(c).getProperties()) { Method m = prop.getReadMethod(); if (m == null || ignore(this, c, m)) continue; JSONHint hint = m.getAnnotation(JSONHint.class); String name = null; int ordinal = prop.getOrdinal(); if (hint != null) { if (hint.ignore()) continue; ordinal = hint.ordinal(); if (hint.name().length() != 0) name = hint.name(); } if (name == null) { name = normalize(prop.getName()); if (getPropertyStyle() != null) { name = getPropertyStyle().to(name); } } if (!name.equals(prop.getName()) || ordinal != prop.getOrdinal()) { map.put(name, new PropertyInfo(prop.getBeanClass(), name, null, prop.getReadMethod(), null, prop.isStatic(), ordinal)); } else { map.put(name, prop); } } props = new ArrayList<PropertyInfo>(map.values()); Collections.sort(props); memberCache.put(c, props); } return props; } @SuppressWarnings("unchecked") Map<String, PropertyInfo> getSetProperties(Class<?> c) { if (memberCache == null) memberCache = new HashMap<Class<?>, Object>(); Map<String, PropertyInfo> props = (Map<String, PropertyInfo>) memberCache.get(c); if (props == null) { Map<String, PropertyInfo> map = new HashMap<String, PropertyInfo>(); // Field for (PropertyInfo prop : BeanInfo.get(c).getProperties()) { Field f = prop.getField(); if (f == null || Modifier.isFinal(f.getModifiers()) || ignore(this, c, f)) continue; JSONHint hint = f.getAnnotation(JSONHint.class); String name = null; int ordinal = prop.getOrdinal(); if (hint != null) { if (hint.ignore()) continue; ordinal = hint.ordinal(); if (hint.name().length() != 0) name = hint.name(); } if (name == null) { name = normalize(prop.getName()); if (getPropertyStyle() != null) { name = getPropertyStyle().to(name); } } if (!name.equals(prop.getName()) || ordinal != prop.getOrdinal() || f != prop.getWriteMember()) { map.put(name, new PropertyInfo(prop.getBeanClass(), name, prop.getField(), null, null, prop.isStatic(), ordinal)); } else { map.put(name, prop); } } // Method for (PropertyInfo prop : BeanInfo.get(c).getProperties()) { Method m = prop.getWriteMethod(); if (m == null || ignore(this, c, m)) continue; JSONHint hint = m.getAnnotation(JSONHint.class); String name = null; int ordinal = prop.getOrdinal(); if (hint != null) { if (hint.ignore()) continue; ordinal = hint.ordinal(); if (hint.name().length() != 0) name = hint.name(); } if (name == null) { name = normalize(prop.getName()); if (getPropertyStyle() != null) { name = getPropertyStyle().to(name); } } if (!name.equals(prop.getName()) || ordinal != prop.getOrdinal()) { map.put(name, new PropertyInfo(prop.getBeanClass(), name, null, null, prop.getWriteMethod(), prop.isStatic(), ordinal)); } else { map.put(name, prop); } } props = map; memberCache.put(c, props); } return props; } NumberFormat getNumberFormat() { JSONHint hint = getHint(); String format = (hint != null && hint.format().length() > 0) ? hint.format() : numberFormat; if (format != null) { NumberFormat nformat = null; if (numberFormatCache == null) { numberFormatCache = new HashMap<String, NumberFormat>(); } else { nformat = numberFormatCache.get(format); } if (nformat == null) { nformat = new DecimalFormat(format, new DecimalFormatSymbols(locale)); numberFormatCache.put(format, nformat); } return nformat; } else { return null; } } DateFormat getDateFormat() { JSONHint hint = getHint(); String format = (hint != null && hint.format().length() > 0) ? hint.format() : dateFormat; if (format != null) { DateFormat dformat = null; if (dateFormatCache == null) { dateFormatCache = new HashMap<String, DateFormat>(); } else { dformat = dateFormatCache.get(format); } if (dformat == null) { dformat = new ExtendedDateFormat(format, locale); dformat.setTimeZone(timeZone); dateFormatCache.put(format, dformat); } return dformat; } else { return null; } } public String toString() { StringBuilderOutputSource sb = new StringBuilderOutputSource(getCachedBuffer()); for (int i = 0; i < path.length; i += 2) { Object key = path[i]; if (key == null) { sb.append("[null]"); } else if (key instanceof Number) { sb.append('['); sb.append(key.toString()); sb.append(']'); } else if (key instanceof Character) { sb.append(key.toString()); } else { String str = key.toString(); boolean escape = false; for (int j = 0; j < str.length(); j++) { if (j == 0) { escape = !Character.isJavaIdentifierStart(str.charAt(j)); } else { escape = !Character.isJavaIdentifierPart(str.charAt(j)); } if (escape) break; } if (escape) { sb.append('['); try { StringFormatter.serialize(this, str, sb); } catch (Exception e) { // no handle } sb.append(']'); } else { sb.append('.'); sb.append(str); } } } return sb.toString(); } } }