    001/* ============
    002 * Orson Charts
    003 * ============
    004 *
    005 * (C)opyright 2013, 2014, by Object Refinery Limited.
    006 *
    007 * http://www.object-refinery.com/orsoncharts/index.html
    008 *
    009 * JSON.simple
    010 * -----------
    011 * The code in this file originates from the JSON.simple project by
    012 * FangYidong<fangyidong@yahoo.com.cn>:
    013 *
    014 *     https://code.google.com/p/json-simple/
    015 *
    016 * which is licensed under the Apache Software License version 2.0.
    017 *
    018 * It has been modified locally and repackaged under
    019 * com.orsoncharts.util.json.* to avoid conflicts with any other version that
    020 * may be present on the classpath.
    021 *
    022 */
    023
    024package com.orsoncharts.util.json;
    025
    026import java.io.IOException;
    027import java.io.Reader;
    028import java.io.StringReader;
    029import java.io.Writer;
    030import java.util.List;
    031import java.util.Map;
    032import com.orsoncharts.util.json.parser.JSONParser;
    033import com.orsoncharts.util.json.parser.ParseException;
    034
    035/**
    036 * Utility methods for JSON values.
    037 */
    038public class JSONValue {
    039
    040    /**
    041     * Parse JSON text into java object from the input source.
    042     * Please use parseWithException() if you don't want to ignore the
    043     * exception.
    044     *
    045     * @see com.orsoncharts.util.json.parser.JSONParser#parse(Reader)
    046     * @see #parseWithException(Reader)
    047     *
    048     * @param in  the input reader.
    049     * @return Instance of the following:
    050     *     com.orsoncharts.util.json.JSONObject,
    051     *     com.orsoncharts.util.json.JSONArray,
    052     *     java.lang.String,
    053     *     java.lang.Number,
    054     *     java.lang.Boolean,
    055     *     null
    056     */
    057    public static Object parse(Reader in){
    058        try {
    059            JSONParser parser = new JSONParser();
    060            return parser.parse(in);
    061        }
    062        catch (Exception e) {
    063            return null;
    064        }
    065    }
    066
    067    /**
    068     * Parses an object from a string.
    069     *
    070     * @param s  the string.
    071     *
    072     * @return An object.
    073     */
    074    public static Object parse(String s){
    075        StringReader in = new StringReader(s);
    076        return parse(in);
    077    }
    078
    079    /**
    080     * Parse JSON text into java object from the input source.
    081     *
    082     * @see com.orsoncharts.util.json.parser.JSONParser
    083     *
    084     * @param in  the input reader ({@code null} not permitted).
    085     *
    086     * @return Instance of the following:
    087     *     com.orsoncharts.util.json.JSONObject,
    088     *     com.orsoncharts.util.json.JSONArray,
    089     *     java.lang.String,
    090     *     java.lang.Number,
    091     *     java.lang.Boolean,
    092     *     null
    093     *
    094     * @throws IOException if there is an I/O problem.
    095     * @throws ParseException if there is a parsing problem.
    096     */
    097    public static Object parseWithException(Reader in) throws IOException,
    098            ParseException{
    099        JSONParser parser = new JSONParser();
    100        return parser.parse(in);
    101    }
    102
    103    /**
    104     * Parses an object from a JSON string.
    105     *
    106     * @param s  the string.
    107     *
    108     * @return An object.
    109     *
    110     * @throws ParseException if there is a parsing problem.
    111     */
    112    public static Object parseWithException(String s) throws ParseException{
    113        JSONParser parser = new JSONParser();
    114        return parser.parse(s);
    115    }
    116
    117    /**
    118     * Encode an object into JSON text and write it to out.
    119     * <p>
    120     * If this object is a {@code Map} or a {@code List}, and it's
    121     * also a {@link JSONStreamAware} or a  {@link JSONAware},
    122     * {@code JSONStreamAware} or {@code JSONAware} will be
    123     * considered firstly.
    124     * <p>
    125     * DO NOT call this method from writeJSONString(Writer) of a class that
    126     * implements both JSONStreamAware and (Map or List) with
    127     * "this" as the first parameter, use JSONObject.writeJSONString(Map,
    128     * Writer) or JSONArray.writeJSONString(List, Writer) instead.
    129     *
    130     * @see com.orsoncharts.util.json.JSONObject#writeJSONString(Map, Writer)
    131     * @see com.orsoncharts.util.json.JSONArray#writeJSONString(List, Writer)
    132     *
    133     * @param value  the value.
    134     * @param out  the output writer.
    135     * @throws IOException if there is an I/O problem.
    136     */
    137    public static void writeJSONString(Object value, Writer out)
    138            throws IOException {
    139        if (value == null) {
    140            out.write("null");
    141            return;
    142        }
    143
    144        if (value instanceof String) {
    145            out.write('\"');
    146            out.write(escape((String) value));
    147            out.write('\"');
    148            return;
    149        }
    150
    151        if (value instanceof Double) {
    152            if(((Double) value).isInfinite() || ((Double) value).isNaN()) {
    153                out.write("null");
    154            }
    155            else {
    156                out.write(value.toString());
    157            }
    158            return;
    159        }
    160
    161        if (value instanceof Float) {
    162            if (((Float) value).isInfinite() || ((Float) value).isNaN()) {
    163                out.write("null");
    164            }
    165            else {
    166                out.write(value.toString());
    167            }
    168            return;
    169        }
    170
    171        if (value instanceof Number) {
    172            out.write(value.toString());
    173            return;
    174        }
    175
    176        if (value instanceof Boolean) {
    177            out.write(value.toString());
    178            return;
    179        }
    180
    181        if ((value instanceof JSONStreamAware)) {
    182            ((JSONStreamAware) value).writeJSONString(out);
    183            return;
    184        }
    185
    186        if ((value instanceof JSONAware)) {
    187            out.write(((JSONAware) value).toJSONString());
    188            return;
    189        }
    190
    191        if (value instanceof Map) {
    192            JSONObject.writeJSONString((Map) value, out);
    193            return;
    194        }
    195
    196        if (value instanceof List) {
    197            JSONArray.writeJSONString((List) value, out);
    198            return;
    199        }
    200
    201        out.write(value.toString());
    202    }
    203
    204    /**
    205     * Convert an object to JSON text.
    206     * <p>
    207     * If this object is a Map or a List, and it's also a JSONAware, JSONAware
    208     * will be considered firstly.
    209     * <p>
    210     * DO NOT call this method from toJSONString() of a class that implements
    211     * both JSONAware and Map or List with
    212     * "this" as the parameter, use JSONObject.toJSONString(Map) or
    213     * JSONArray.toJSONString(List) instead.
    214     *
    215     * @see com.orsoncharts.util.json.JSONObject#toJSONString(Map)
    216     * @see com.orsoncharts.util.json.JSONArray#toJSONString(List)
    217     *
    218     * @param value the value.
    219     * @return JSON text, or "null" if value is null or it's an NaN or an INF
    220     * number.
    221     */
    222    public static String toJSONString(Object value){
    223        if (value == null) {
    224            return "null";
    225        }
    226
    227        if (value instanceof String) {
    228            return "\"" + escape((String) value) + "\"";
    229        }
    230
    231        if (value instanceof Double){
    232            if(((Double) value).isInfinite() || ((Double) value).isNaN()) {
    233                return "null";
    234            }
    235            else {
    236                return value.toString();
    237            }
    238        }
    239
    240        if (value instanceof Float) {
    241            if (((Float) value).isInfinite() || ((Float) value).isNaN()) {
    242                return "null";
    243            }
    244            else {
    245                return value.toString();
    246            }
    247        }
    248
    249        if (value instanceof Number) {
    250            return value.toString();
    251        }
    252
    253        if (value instanceof Boolean) {
    254            return value.toString();
    255        }
    256
    257        if ((value instanceof JSONAware)) {
    258            return ((JSONAware) value).toJSONString();
    259        }
    260
    261        if (value instanceof Map) {
    262            return JSONObject.toJSONString((Map) value);
    263        }
    264
    265        if (value instanceof List) {
    266            return JSONArray.toJSONString((List) value);
    267        }
    268
    269        return value.toString();
    270    }
    271
    272    /**
    273     * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters
    274     * (U+0000 through U+001F).
    275     *
    276     * @param s  the string to be escaped ({@code null} permitted).
    277     *
    278     * @return A string.
    279     */
    280    public static String escape(String s) {
    281        if (s == null) {
    282            return null;
    283        }
    284        StringBuffer sb = new StringBuffer();
    285        escape(s, sb);
    286        return sb.toString();
    287    }
    288
    289    /**
    290     * @param s - Must not be null.
    291     * @param sb
    292     */
    293    static void escape(String s, StringBuffer sb) {
    294        for(int i = 0; i < s.length(); i++) {
    295            char ch = s.charAt(i);
    296            switch(ch){
    297            case '"':
    298                sb.append("\\\"");
    299                break;
    300            case '\\':
    301                sb.append("\\\\");
    302                break;
    303            case '\b':
    304                sb.append("\\b");
    305                break;
    306            case '\f':
    307                sb.append("\\f");
    308                break;
    309            case '\n':
    310                sb.append("\\n");
    311                break;
    312            case '\r':
    313                sb.append("\\r");
    314                break;
    315            case '\t':
    316                sb.append("\\t");
    317                break;
    318            case '/':
    319                sb.append("\\/");
    320                break;
    321            default:
    322                //Reference: http://www.unicode.org/versions/Unicode5.1.0/
    323                if ((ch >= '\u0000' && ch <= '\u001F')
    324                        || (ch >= '\u007F' && ch <= '\u009F')
    325                        || (ch >= '\u2000' && ch <= '\u20FF')) {
    326                    String ss = Integer.toHexString(ch);
    327                    sb.append("\\u");
    328                    for (int k = 0; k < 4 - ss.length(); k++) {
    329                        sb.append('0');
    330                    }
    331                    sb.append(ss.toUpperCase());
    332                }
    333                else {
    334                    sb.append(ch);
    335                }
    336            }
    337        }//for
    338    }
    339
    340}