zcommon/src/main/java/org/zkoss/json/parser/JSONParser.java

Summary

Maintainability
D
2 days
Test Coverage
/*
 * $Id: JSONParser.java,v 1.1 2006/04/15 14:10:48 platform Exp $
 * Created on 2006-4-15
 */
package org.zkoss.json.parser;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.zkoss.json.JSONArray;
import org.zkoss.json.JSONObject;


/**
 * Parser for JSON text. Please note that JSONParser is NOT thread-safe.
 * 
 * @author FangYidong<fangyidong@yahoo.com.cn>
 */
@SuppressWarnings("unchecked")
public class JSONParser {
    public static final int S_INIT=0;
    public static final int S_IN_FINISHED_VALUE=1;//string,number,boolean,null,object,array
    public static final int S_IN_OBJECT=2;
    public static final int S_IN_ARRAY=3;
    public static final int S_PASSED_PAIR_KEY=4;
    public static final int S_IN_PAIR_VALUE=5;
    public static final int S_END=6;
    public static final int S_IN_ERROR=-1;
    
    private LinkedList handlerStatusStack;
    private Yylex lexer = new Yylex((Reader)null);
    private Yytoken token = null;
    private int status = S_INIT;
    
    private int peekStatus(LinkedList statusStack){
        if(statusStack.size()==0)
            return -1;
        Integer status=(Integer)statusStack.getFirst();
        return status.intValue();
    }
    
    /**
     *  Reset the parser to the initial state without resetting the underlying reader.
     *
     */
    public void reset(){
        token = null;
        status = S_INIT;
        handlerStatusStack = null;
    }
    
    /**
     * Reset the parser to the initial state with a new character reader.
     * 
     * @param in - The new character reader.
     * @throws IOException
     * @throws ParseException
     */
    public void reset(Reader in){
        lexer.yyreset(in);
        reset();
    }
    
    /**
     * @return The position of the beginning of the current token.
     */
    public int getPosition(){
        return lexer.getPosition();
    }
    
    public Object parse(String s) throws ParseException{
        return parse(s, (ContainerFactory)null);
    }
    
    public Object parse(String s, ContainerFactory containerFactory) throws ParseException{
        StringReader in=new StringReader(s);
        try{
            return parse(in, containerFactory);
        }
        catch(IOException ie){
            /*
             * Actually it will never happen.
             */
            throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie);
        }
    }
    
    public Object parse(Reader in) throws IOException, ParseException{
        return parse(in, (ContainerFactory)null);
    }
    
    /**
     * Parse JSON text into java object from the input source.
     *     
     * @param in
     * @param containerFactory - Use this factory to create your own JSON object and JSON array containers.
     * @return Instance of the following:
     *  org.zkoss.json.JSONObject,
     *     org.zkoss.json.JSONArray,
     *     java.lang.String,
     *     java.lang.Number,
     *     java.lang.Boolean,
     *     null
     * 
     * @throws IOException
     * @throws ParseException
     */
    public Object parse(Reader in, ContainerFactory containerFactory) throws IOException, ParseException{
        reset(in);
        LinkedList statusStack = new LinkedList();
        LinkedList valueStack = new LinkedList();
        
        try{
            do{
                nextToken();
                switch(status){
                case S_INIT:
                    switch(token.type){
                    case Yytoken.TYPE_VALUE:
                        status=S_IN_FINISHED_VALUE;
                        statusStack.addFirst(new Integer(status));
                        valueStack.addFirst(token.value);
                        break;
                    case Yytoken.TYPE_LEFT_BRACE:
                        status=S_IN_OBJECT;
                        statusStack.addFirst(new Integer(status));
                        valueStack.addFirst(createObjectContainer(containerFactory));
                        break;
                    case Yytoken.TYPE_LEFT_SQUARE:
                        status=S_IN_ARRAY;
                        statusStack.addFirst(new Integer(status));
                        valueStack.addFirst(createArrayContainer(containerFactory));
                        break;
                    default:
                        status=S_IN_ERROR;
                    }//inner switch
                    break;
                    
                case S_IN_FINISHED_VALUE:
                    if(token.type==Yytoken.TYPE_EOF)
                        return valueStack.removeFirst();
                    else
                        throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
                    
                case S_IN_OBJECT:
                    switch(token.type){
                    case Yytoken.TYPE_COMMA:
                        break;
                    case Yytoken.TYPE_VALUE:
                        if(token.value instanceof String){
                            String key=(String)token.value;
                            valueStack.addFirst(key);
                            status=S_PASSED_PAIR_KEY;
                            statusStack.addFirst(new Integer(status));
                        }
                        else{
                            status=S_IN_ERROR;
                        }
                        break;
                    case Yytoken.TYPE_RIGHT_BRACE:
                        if(valueStack.size()>1){
                            statusStack.removeFirst();
                            valueStack.removeFirst();
                            status=peekStatus(statusStack);
                        }
                        else{
                            status=S_IN_FINISHED_VALUE;
                        }
                        break;
                    default:
                        status=S_IN_ERROR;
                        break;
                    }//inner switch
                    break;
                    
                case S_PASSED_PAIR_KEY:
                    switch(token.type){
                    case Yytoken.TYPE_COLON:
                        break;
                    case Yytoken.TYPE_VALUE:
                        statusStack.removeFirst();
                        String key=(String)valueStack.removeFirst();
                        Map parent=(Map)valueStack.getFirst();
                        parent.put(key,token.value);
                        status=peekStatus(statusStack);
                        break;
                    case Yytoken.TYPE_LEFT_SQUARE:
                        statusStack.removeFirst();
                        key=(String)valueStack.removeFirst();
                        parent=(Map)valueStack.getFirst();
                        List newArray=createArrayContainer(containerFactory);
                        parent.put(key,newArray);
                        status=S_IN_ARRAY;
                        statusStack.addFirst(new Integer(status));
                        valueStack.addFirst(newArray);
                        break;
                    case Yytoken.TYPE_LEFT_BRACE:
                        statusStack.removeFirst();
                        key=(String)valueStack.removeFirst();
                        parent=(Map)valueStack.getFirst();
                        Map newObject=createObjectContainer(containerFactory);
                        parent.put(key,newObject);
                        status=S_IN_OBJECT;
                        statusStack.addFirst(new Integer(status));
                        valueStack.addFirst(newObject);
                        break;
                    default:
                        status=S_IN_ERROR;
                    }
                    break;
                    
                case S_IN_ARRAY:
                    switch(token.type){
                    case Yytoken.TYPE_COMMA:
                        break;
                    case Yytoken.TYPE_VALUE:
                        List val=(List)valueStack.getFirst();
                        val.add(token.value);
                        break;
                    case Yytoken.TYPE_RIGHT_SQUARE:
                        if(valueStack.size()>1){
                            statusStack.removeFirst();
                            valueStack.removeFirst();
                            status=peekStatus(statusStack);
                        }
                        else{
                            status=S_IN_FINISHED_VALUE;
                        }
                        break;
                    case Yytoken.TYPE_LEFT_BRACE:
                        val=(List)valueStack.getFirst();
                        Map newObject=createObjectContainer(containerFactory);
                        val.add(newObject);
                        status=S_IN_OBJECT;
                        statusStack.addFirst(new Integer(status));
                        valueStack.addFirst(newObject);
                        break;
                    case Yytoken.TYPE_LEFT_SQUARE:
                        val=(List)valueStack.getFirst();
                        List newArray=createArrayContainer(containerFactory);
                        val.add(newArray);
                        status=S_IN_ARRAY;
                        statusStack.addFirst(new Integer(status));
                        valueStack.addFirst(newArray);
                        break;
                    default:
                        status=S_IN_ERROR;
                    }//inner switch
                    break;
                case S_IN_ERROR:
                    throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
                }//switch
                if(status==S_IN_ERROR){
                    throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
                }
            }while(token.type!=Yytoken.TYPE_EOF);
        }
        catch(IOException ie){
            throw ie;
        }
        
        throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token);
    }
    
    private void nextToken() throws ParseException, IOException{
        token = lexer.yylex();
        if(token == null)
            token = new Yytoken(Yytoken.TYPE_EOF, null);
    }
    
    private Map createObjectContainer(ContainerFactory containerFactory){
        if(containerFactory == null)
            return new JSONObject();
        Map m = containerFactory.createObjectContainer();
        
        if(m == null)
            return new JSONObject();
        return m;
    }
    
    private List createArrayContainer(ContainerFactory containerFactory){
        if(containerFactory == null)
            return new JSONArray();
        List l = containerFactory.creatArrayContainer();
        
        if(l == null)
            return new JSONArray();
        return l;
    }
}