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