# JsonStreamSourceImpl.java

    /*
     * Copyright 2011, 2012 Odysseus Software GmbH
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package org.apache.synapse.commons.staxon.core.json.stream.impl;

    import java.io.Closeable;
    import java.io.IOException;
    import java.math.BigDecimal;
    import java.math.BigInteger;

    import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamSource;
    import org.apache.synapse.commons.staxon.core.json.stream.JsonStreamToken;

    /**
     * Default <code>JsonStreamSource</code> implementation.
     */
    class JsonStreamSourceImpl implements JsonStreamSource {
        /**
         * Scanner interface
         */
        interface Scanner extends Closeable {
            enum Symbol {
                START_OBJECT("START_OBJECT"),
                SO_ARRAY("SO_ARRAY"),
                SO_ELEMENT("SO_ELEMENT"),
                SO_COLON_1("SO_COLON_1"),
                SO_COLON_2("SO_COLON_2"),
                SO_ARRAY_END("SO_ARRAY_END"),
                SO_ARRAY_END_2("SO_ARRAY_END_2"),
                SO_END("SO_END"),
                SO_END_2("SO_END_2"),
                SO_OBJECT("SO_OBJECT"),
                SO_OBJECT_COL("SO_OBJECT_COL"),
                SO_OBJECT_END("SO_OBJECT_END"),
                END_OBJECT("END_OBJECT"),
                START_ARRAY("START_ARRAY"),
                END_ARRAY("END_ARRAY"),
                COLON("COLON"),
                COMMA("COMMA"),
                STRING("STRING"),
                NUMBER("NUMBER"),
                TRUE("TRUE"),
                FALSE("FALSE"),
                NULL("NULL"),
                EMPTY_OBJ_NAME("EMPTY_OBJ_NAME"),
                EMPTY_OBJ_VALUE("EMPTY_OBJ_VALUE"),
                EMPTY_OBJ_END("EMPTY_OBJ_END"),
                EOF("EOF"),
                EOF_OBJ("EOF_OBJ");

                private String name = null;

                Symbol(String name) {
                    this.name = name;
                }

                public String toString() {
                    return name;
                }
            }

            Symbol nextSymbol() throws IOException;

            String getText();

            int getCharOffset();

            int getLineNumber();

            int getColumnNumber();
        }

        private final Scanner scanner;
        private final boolean[] arrays = new boolean[64];
        private final boolean closeScanner;

        private JsonStreamToken token = null;
        private Scanner.Symbol symbol = null;
        private int depth = 0;
        private boolean peeked = false;

        private int lineNumber;
        private int columnNumber;
        private int charOffset;

        JsonStreamSourceImpl(Scanner scanner, boolean closeScanner) {
            this.scanner = scanner;
            this.closeScanner = closeScanner;
            this.lineNumber = scanner.getLineNumber();
            this.columnNumber = scanner.getColumnNumber();
            this.charOffset = scanner.getCharOffset();
        }

        private JsonStreamToken startJsonValue() throws IOException {
            switch (symbol) {
                case FALSE:
                case NULL:
                case NUMBER:
                case TRUE:
                case STRING:
                    return JsonStreamToken.VALUE;
                case START_ARRAY:
                    if (arrays[depth]) {
                        throw new IOException("Already in an array");
                    }
                    arrays[depth] = true;
                    return JsonStreamToken.START_ARRAY;
                case START_OBJECT:
                    depth++;
                    return JsonStreamToken.START_OBJECT;
                default:
                    throw new IOException("Unexpected symbol: " + symbol);
            }
        }

        private void require(Scanner.Symbol expected) throws IOException {
            if (symbol != expected) {
                throw new IOException("Unexpected symbol:" + symbol);
            }
        }

        private JsonStreamToken next() throws IOException {
            symbol = scanner.nextSymbol();
            if (symbol == Scanner.Symbol.EOF) {
                if (depth != 0 || arrays[depth]) {
                    throw new IOException("Premature EOF");
                }
                return JsonStreamToken.NONE;
            }
            if (token == null) {
                return startJsonValue();
            }
            switch (token) {
                case NAME:
                    require(Scanner.Symbol.COLON);
                    symbol = scanner.nextSymbol();
                    return startJsonValue();
                case END_OBJECT:
                case END_ARRAY:
                case VALUE:
                    switch (symbol) {
                        case COMMA:
                            symbol = scanner.nextSymbol();
                            if (arrays[depth]) {
                                return startJsonValue();
                            } else {
                                require(Scanner.Symbol.STRING);
                                return JsonStreamToken.NAME;
                            }
                        case END_ARRAY:
                            if (!arrays[depth]) {
                                throw new IOException("Not in an array");
                            }
                            arrays[depth] = false;
                            return JsonStreamToken.END_ARRAY;
                        case END_OBJECT:
                            if (arrays[depth]) {
                                throw new IOException("Unclosed array");
                            }
                            if (depth == 0) {
                                throw new IOException("Not in an object");
                            }
                            depth--;
                            return JsonStreamToken.END_OBJECT;
                        default:
                            throw new IOException("Unexpected symbol: " + symbol);
                    }
                case START_OBJECT:
                    switch (symbol) {
                        case END_OBJECT:
                            depth--;
                            return JsonStreamToken.END_OBJECT;
                        case STRING:
                            return JsonStreamToken.NAME;
                        default:
                            throw new IOException("Unexpected symbol: " + symbol);
                    }
                case START_ARRAY:
                    switch (symbol) {
                        case END_ARRAY:
                            arrays[depth] = false;
                            return JsonStreamToken.END_ARRAY;
                        default:
                            return startJsonValue();
                    }
                default:
                    throw new IOException("Unexpected token: " + token);
            }
        }

        public void close() throws IOException {
            if (closeScanner) {
                scanner.close();
            }
        }

        /**
         * Make the next token the current token.
         * Save location info from scanner to prevent changing location by peek()
         *
         * @param token expected token
         * @throws IOException
         */
        private void poll(JsonStreamToken token) throws IOException {
            if (token != peek()) {
                throw new IOException("Unexpected token: " + peek());
            }
            lineNumber = scanner.getLineNumber();
            columnNumber = scanner.getColumnNumber();
            charOffset = scanner.getCharOffset();
            peeked = false;
        }

        public String name() throws IOException {
            poll(JsonStreamToken.NAME);
            return scanner.getText();
        }

        public Value value() throws IOException {
            poll(JsonStreamToken.VALUE);
            switch (symbol) {
                case NULL:
                    return NULL;
                case STRING:
                    return new Value(scanner.getText());
                case TRUE:
                    return TRUE;
                case FALSE:
                    return FALSE;
                case NUMBER:
                    if (scanner.getText().indexOf('.') < 0 && scanner.getText().toLowerCase().indexOf('e') < 0) {
                        return new Value(scanner.getText(), new BigInteger(scanner.getText()));
                    } else {
                        return new Value(scanner.getText(), new BigDecimal(scanner.getText()));
                    }
                default:
                    throw new IOException("Not a value token: " + symbol);
            }
        }

        public void startObject() throws IOException {
            poll(JsonStreamToken.START_OBJECT);
        }

        public void endObject() throws IOException {
            poll(JsonStreamToken.END_OBJECT);
        }

        public void startArray() throws IOException {
            poll(JsonStreamToken.START_ARRAY);
        }

        public void endArray() throws IOException {
            poll(JsonStreamToken.END_ARRAY);
        }

        public JsonStreamToken peek() throws IOException {
            if (!peeked) {
                token = next();
                peeked = true;
            }
            return token;
        }

        public int getLineNumber() {
            return lineNumber + 1;
        }

        public int getColumnNumber() {
            return columnNumber + 1;
        }

        public int getCharacterOffset() {
            return charOffset;
        }

        public String getPublicId() {
            return null;
        }

        public String getSystemId() {
            return null;
        }
    }