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}