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.parser;
025
026import java.io.IOException;
027import java.io.Reader;
028import java.io.StringReader;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Map;
032import com.orsoncharts.util.json.JSONArray;
033import com.orsoncharts.util.json.JSONObject;
034
035
036/**
037 * Parser for JSON text. Please note that JSONParser is NOT thread-safe.
038 */
039public class JSONParser {
040    
041    public static final int S_INIT = 0;
042    public static final int S_IN_FINISHED_VALUE = 1;//string,number,boolean,null,object,array
043    public static final int S_IN_OBJECT = 2;
044    public static final int S_IN_ARRAY = 3;
045    public static final int S_PASSED_PAIR_KEY = 4;
046    public static final int S_IN_PAIR_VALUE = 5;
047    public static final int S_END = 6;
048    public static final int S_IN_ERROR = -1;
049    
050    private LinkedList handlerStatusStack;
051    private Yylex lexer = new Yylex((Reader)null);
052    private Yytoken token = null;
053    private int status = S_INIT;
054    
055    private int peekStatus(LinkedList statusStack){
056        if (statusStack.size() == 0) {
057            return -1;
058        }
059        Integer result = (Integer) statusStack.getFirst();
060        return result.intValue();
061    }
062    
063    /**
064     * Reset the parser to the initial state without resetting the underlying 
065     * reader.
066     */
067    public void reset(){
068        token = null;
069        status = S_INIT;
070        handlerStatusStack = null;
071    }
072    
073    /**
074     * Reset the parser to the initial state with a new character reader.
075     * 
076     * @param in  the new character reader.
077     */
078    public void reset(Reader in){
079        lexer.yyreset(in);
080        reset();
081    }
082    
083    /**
084     * @return The position of the beginning of the current token.
085     */
086    public int getPosition(){
087        return lexer.getPosition();
088    }
089    
090    public Object parse(String s) throws ParseException {
091        return parse(s, (ContainerFactory) null);
092    }
093    
094    public Object parse(String s, ContainerFactory containerFactory) 
095            throws ParseException {
096        StringReader in = new StringReader(s);
097        try {
098            return parse(in, containerFactory);
099        }
100        catch(IOException ie){
101            /*
102             * Actually it will never happen.
103             */
104            throw new ParseException(-1, 
105                    ParseException.ERROR_UNEXPECTED_EXCEPTION, ie);
106        }
107    }
108    
109    public Object parse(Reader in) throws IOException, ParseException {
110        return parse(in, (ContainerFactory) null);
111    }
112    
113    /**
114     * Parse JSON text into java object from the input source.
115     *     
116     * @param in  the input source.
117     * @param containerFactory  use this factory to create your own JSON 
118     *     object and JSON array containers.
119     * @return Instance of the following:
120     *     com.orsoncharts.util.json.simple.JSONObject,
121     *     com.orsoncharts.util.json.simple.JSONArray,
122     *     java.lang.String,
123     *     java.lang.Number,
124     *     java.lang.Boolean,
125     *     null
126     * 
127     * @throws IOException if there is an I/O problem.
128     * @throws ParseException if there is a parsing problem.
129     */
130    public Object parse(Reader in, ContainerFactory containerFactory) 
131            throws IOException, ParseException{
132        reset(in);
133        LinkedList statusStack = new LinkedList();
134        LinkedList valueStack = new LinkedList();
135        
136        try {
137            do {
138                nextToken();
139                switch(status) {
140                case S_INIT:
141                    switch(token.type) {
142                    case Yytoken.TYPE_VALUE:
143                        status=S_IN_FINISHED_VALUE;
144                        statusStack.addFirst(new Integer(status));
145                        valueStack.addFirst(token.value);
146                        break;
147                    case Yytoken.TYPE_LEFT_BRACE:
148                        status=S_IN_OBJECT;
149                        statusStack.addFirst(new Integer(status));
150                        valueStack.addFirst(createObjectContainer(containerFactory));
151                        break;
152                    case Yytoken.TYPE_LEFT_SQUARE:
153                        status=S_IN_ARRAY;
154                        statusStack.addFirst(new Integer(status));
155                        valueStack.addFirst(createArrayContainer(containerFactory));
156                        break;
157                    default:
158                        status=S_IN_ERROR;
159                    }//inner switch
160                    break;
161                    
162                case S_IN_FINISHED_VALUE:
163                    if (token.type == Yytoken.TYPE_EOF) {
164                        return valueStack.removeFirst();
165                    } else {
166                    throw new ParseException(getPosition(), 
167                            ParseException.ERROR_UNEXPECTED_TOKEN, token);
168                }
169                    
170                case S_IN_OBJECT:
171                    switch(token.type) {
172                    case Yytoken.TYPE_COMMA:
173                        break;
174                    case Yytoken.TYPE_VALUE:
175                        if (token.value instanceof String) {
176                            String key = (String) token.value;
177                            valueStack.addFirst(key);
178                            status = S_PASSED_PAIR_KEY;
179                            statusStack.addFirst(new Integer(status));
180                        }
181                        else{
182                            status = S_IN_ERROR;
183                        }
184                        break;
185                    case Yytoken.TYPE_RIGHT_BRACE:
186                        if (valueStack.size() > 1) {
187                            statusStack.removeFirst();
188                            valueStack.removeFirst();
189                            status = peekStatus(statusStack);
190                        }
191                        else{
192                            status = S_IN_FINISHED_VALUE;
193                        }
194                        break;
195                    default:
196                        status = S_IN_ERROR;
197                        break;
198                    }//inner switch
199                    break;
200                    
201                case S_PASSED_PAIR_KEY:
202                    switch(token.type) {
203                    case Yytoken.TYPE_COLON:
204                        break;
205                    case Yytoken.TYPE_VALUE:
206                        statusStack.removeFirst();
207                        String key = (String) valueStack.removeFirst();
208                        Map parent = (Map) valueStack.getFirst();
209                        parent.put(key,token.value);
210                        status = peekStatus(statusStack);
211                        break;
212                    case Yytoken.TYPE_LEFT_SQUARE:
213                        statusStack.removeFirst();
214                        key = (String) valueStack.removeFirst();
215                        parent = (Map) valueStack.getFirst();
216                        List newArray = createArrayContainer(containerFactory);
217                        parent.put(key,newArray);
218                        status = S_IN_ARRAY;
219                        statusStack.addFirst(new Integer(status));
220                        valueStack.addFirst(newArray);
221                        break;
222                    case Yytoken.TYPE_LEFT_BRACE:
223                        statusStack.removeFirst();
224                        key = (String) valueStack.removeFirst();
225                        parent = (Map) valueStack.getFirst();
226                        Map newObject = createObjectContainer(containerFactory);
227                        parent.put(key, newObject);
228                        status = S_IN_OBJECT;
229                        statusStack.addFirst(new Integer(status));
230                        valueStack.addFirst(newObject);
231                        break;
232                    default:
233                        status = S_IN_ERROR;
234                    }
235                    break;
236                    
237                case S_IN_ARRAY:
238                    switch(token.type) {
239                    case Yytoken.TYPE_COMMA:
240                        break;
241                    case Yytoken.TYPE_VALUE:
242                        List val = (List) valueStack.getFirst();
243                        val.add(token.value);
244                        break;
245                    case Yytoken.TYPE_RIGHT_SQUARE:
246                        if (valueStack.size() > 1) {
247                            statusStack.removeFirst();
248                            valueStack.removeFirst();
249                            status = peekStatus(statusStack);
250                        }
251                        else {
252                            status = S_IN_FINISHED_VALUE;
253                        }
254                        break;
255                    case Yytoken.TYPE_LEFT_BRACE:
256                        val = (List) valueStack.getFirst();
257                        Map newObject = createObjectContainer(containerFactory);
258                        val.add(newObject);
259                        status = S_IN_OBJECT;
260                        statusStack.addFirst(new Integer(status));
261                        valueStack.addFirst(newObject);
262                        break;
263                    case Yytoken.TYPE_LEFT_SQUARE:
264                        val = (List) valueStack.getFirst();
265                        List newArray = createArrayContainer(containerFactory);
266                        val.add(newArray);
267                        status = S_IN_ARRAY;
268                        statusStack.addFirst(new Integer(status));
269                        valueStack.addFirst(newArray);
270                        break;
271                    default:
272                        status = S_IN_ERROR;
273                    }//inner switch
274                    break;
275                case S_IN_ERROR:
276                    throw new ParseException(getPosition(), 
277                            ParseException.ERROR_UNEXPECTED_TOKEN, token);
278                }//switch
279                if (status == S_IN_ERROR) {
280                    throw new ParseException(getPosition(), 
281                            ParseException.ERROR_UNEXPECTED_TOKEN, token);
282                }
283            } while (token.type != Yytoken.TYPE_EOF);
284        }
285        catch (IOException ie) {
286            throw ie;
287        }
288        
289        throw new ParseException(getPosition(), 
290                ParseException.ERROR_UNEXPECTED_TOKEN, token);
291    }
292    
293    private void nextToken() throws ParseException, IOException {
294        token = lexer.yylex();
295        if (token == null) {
296            token = new Yytoken(Yytoken.TYPE_EOF, null);
297        }
298    }
299    
300    private Map createObjectContainer(ContainerFactory containerFactory) {
301        if (containerFactory == null) {
302            return new JSONObject();
303        }
304        Map m = containerFactory.createObjectContainer();
305        if (m == null) {
306            return new JSONObject();
307        }
308        return m;
309    }
310    
311    private List createArrayContainer(ContainerFactory containerFactory) {
312        if (containerFactory == null) {
313            return new JSONArray();
314        }
315        List l = containerFactory.creatArrayContainer();
316        if (l == null) {
317            return new JSONArray();
318        }
319        return l;
320    }
321    
322    public void parse(String s, ContentHandler contentHandler) 
323            throws ParseException {
324        parse(s, contentHandler, false);
325    }
326    
327    public void parse(String s, ContentHandler contentHandler, boolean isResume)
328            throws ParseException{
329        StringReader in = new StringReader(s);
330        try{
331            parse(in, contentHandler, isResume);
332        }
333        catch(IOException ie) {
334            /*
335             * Actually it will never happen.
336             */
337            throw new ParseException(-1, 
338                    ParseException.ERROR_UNEXPECTED_EXCEPTION, ie);
339        }
340    }
341    
342    public void parse(Reader in, ContentHandler contentHandler) 
343            throws IOException, ParseException {
344        parse(in, contentHandler, false);
345    }
346    
347    /**
348     * Stream processing of JSON text.
349     * 
350     * @see ContentHandler
351     * 
352     * @param in  the input.
353     * @param contentHandler  the content handler.
354     * @param isResume  indicates if it continues previous parsing operation.
355     *     If set to true, resume parsing the old stream, and parameter 'in' 
356     *     will be ignored.  If this method is called for the first time in 
357     *     this instance, isResume will be ignored.
358     * 
359     * @throws IOException if there is an I/O problem.
360     * @throws ParseException if there is a parsing problem.
361     */
362    public void parse(Reader in, ContentHandler contentHandler, 
363            boolean isResume) throws IOException, ParseException {
364        if (!isResume) {
365            reset(in);
366            handlerStatusStack = new LinkedList();
367        }
368        else{
369            if (handlerStatusStack == null) {
370                isResume = false;
371                reset(in);
372                handlerStatusStack = new LinkedList();
373            }
374        }
375        
376        LinkedList statusStack = handlerStatusStack;    
377        
378        try {
379            do {
380                switch(status) {
381                case S_INIT:
382                    contentHandler.startJSON();
383                    nextToken();
384                    switch(token.type) {
385                    case Yytoken.TYPE_VALUE:
386                        status=S_IN_FINISHED_VALUE;
387                        statusStack.addFirst(new Integer(status));
388                        if (!contentHandler.primitive(token.value)) {
389                            return;
390                        }
391                        break;
392                    case Yytoken.TYPE_LEFT_BRACE:
393                        status = S_IN_OBJECT;
394                        statusStack.addFirst(new Integer(status));
395                        if (!contentHandler.startObject()) {
396                            return;
397                        }
398                        break;
399                    case Yytoken.TYPE_LEFT_SQUARE:
400                        status = S_IN_ARRAY;
401                        statusStack.addFirst(new Integer(status));
402                        if (!contentHandler.startArray()) {
403                            return;
404                        }
405                        break;
406                    default:
407                        status = S_IN_ERROR;
408                    }//inner switch
409                    break;
410                    
411                case S_IN_FINISHED_VALUE:
412                    nextToken();
413                    if (token.type == Yytoken.TYPE_EOF) {
414                        contentHandler.endJSON();
415                        status = S_END;
416                        return;
417                    }
418                    else {
419                        status = S_IN_ERROR;
420                        throw new ParseException(getPosition(), 
421                                ParseException.ERROR_UNEXPECTED_TOKEN, token);
422                    }
423            
424                case S_IN_OBJECT:
425                    nextToken();
426                    switch(token.type) {
427                    case Yytoken.TYPE_COMMA:
428                        break;
429                    case Yytoken.TYPE_VALUE:
430                        if (token.value instanceof String) {
431                            String key = (String) token.value;
432                            status = S_PASSED_PAIR_KEY;
433                            statusStack.addFirst(new Integer(status));
434                            if (!contentHandler.startObjectEntry(key)) {
435                                return;
436                            }
437                        }
438                        else {
439                            status = S_IN_ERROR;
440                        }
441                        break;
442                    case Yytoken.TYPE_RIGHT_BRACE:
443                        if (statusStack.size() > 1) {
444                            statusStack.removeFirst();
445                            status = peekStatus(statusStack);
446                        }
447                        else{
448                            status = S_IN_FINISHED_VALUE;
449                        }
450                        if (!contentHandler.endObject()) {
451                            return;
452                        }
453                        break;
454                    default:
455                        status = S_IN_ERROR;
456                        break;
457                    }//inner switch
458                    break;
459                    
460                case S_PASSED_PAIR_KEY:
461                    nextToken();
462                    switch(token.type) {
463                    case Yytoken.TYPE_COLON:
464                        break;
465                    case Yytoken.TYPE_VALUE:
466                        statusStack.removeFirst();
467                        status = peekStatus(statusStack);
468                        if (!contentHandler.primitive(token.value)) {
469                            return;
470                        }
471                        if (!contentHandler.endObjectEntry()) {
472                            return;
473                        }
474                        break;
475                    case Yytoken.TYPE_LEFT_SQUARE:
476                        statusStack.removeFirst();
477                        statusStack.addFirst(new Integer(S_IN_PAIR_VALUE));
478                        status = S_IN_ARRAY;
479                        statusStack.addFirst(new Integer(status));
480                        if (!contentHandler.startArray()) {
481                            return;
482                        }
483                        break;
484                    case Yytoken.TYPE_LEFT_BRACE:
485                        statusStack.removeFirst();
486                        statusStack.addFirst(new Integer(S_IN_PAIR_VALUE));
487                        status = S_IN_OBJECT;
488                        statusStack.addFirst(new Integer(status));
489                        if (!contentHandler.startObject()) {
490                            return;
491                        }
492                        break;
493                    default:
494                        status = S_IN_ERROR;
495                    }
496                    break;
497                
498                case S_IN_PAIR_VALUE:
499                    /*
500                     * S_IN_PAIR_VALUE is just a marker to indicate the end of 
501                     * an object entry, it doesn't proccess any token,
502                     * therefore delay consuming token until next round.
503                     */
504                    statusStack.removeFirst();
505                    status = peekStatus(statusStack);
506                    if (!contentHandler.endObjectEntry()) {
507                        return;
508                    }
509                    break;
510                    
511                case S_IN_ARRAY:
512                    nextToken();
513                    switch(token.type){
514                    case Yytoken.TYPE_COMMA:
515                        break;
516                    case Yytoken.TYPE_VALUE:
517                        if (!contentHandler.primitive(token.value)) {
518                            return;
519                        }
520                        break;
521                    case Yytoken.TYPE_RIGHT_SQUARE:
522                        if (statusStack.size() > 1) {
523                            statusStack.removeFirst();
524                            status = peekStatus(statusStack);
525                        }
526                        else{
527                            status = S_IN_FINISHED_VALUE;
528                        }
529                        if (!contentHandler.endArray()) {
530                            return;
531                        }
532                        break;
533                    case Yytoken.TYPE_LEFT_BRACE:
534                        status = S_IN_OBJECT;
535                        statusStack.addFirst(new Integer(status));
536                        if (!contentHandler.startObject()) {
537                            return;
538                        }
539                        break;
540                    case Yytoken.TYPE_LEFT_SQUARE:
541                        status = S_IN_ARRAY;
542                        statusStack.addFirst(new Integer(status));
543                        if (!contentHandler.startArray()) {
544                            return;
545                        }
546                        break;
547                    default:
548                        status = S_IN_ERROR;
549                    }//inner switch
550                    break;
551                    
552                case S_END:
553                    return;
554                    
555                case S_IN_ERROR:
556                    throw new ParseException(getPosition(), 
557                            ParseException.ERROR_UNEXPECTED_TOKEN, token);
558                }//switch
559                if (status == S_IN_ERROR) {
560                    throw new ParseException(getPosition(), 
561                            ParseException.ERROR_UNEXPECTED_TOKEN, token);
562                }
563            } while(token.type != Yytoken.TYPE_EOF);
564        }
565        catch (IOException ie) {
566            status = S_IN_ERROR;
567            throw ie;
568        }
569        catch (ParseException pe) {
570            status = S_IN_ERROR;
571            throw pe;
572        }
573        catch (RuntimeException re) {
574            status = S_IN_ERROR;
575            throw re;
576        }
577        catch (Error e) {
578            status = S_IN_ERROR;
579            throw e;
580        }
581        
582        status = S_IN_ERROR;
583        throw new ParseException(getPosition(), 
584                ParseException.ERROR_UNEXPECTED_TOKEN, token);
585    }
586}