All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.brettonw.bag.json.FormatReaderJson Maven / Gradle / Ivy

package com.brettonw.bag.json;

// The FormatReaderJson is loosely modeled after a JSON parser grammar write the site (http://www.json.org).
// The main difference is that we ignore differences between value types (all of them will be
// strings internally), and assume the input is a well formed string representation of a BagObject
// or BagArray in JSON-ish format

import com.brettonw.bag.BagArray;
import com.brettonw.bag.BagObject;
import com.brettonw.bag.FormatReader;

import java.util.Arrays;

public class FormatReaderJson extends FormatReader {
    public static final String JSON_FORMAT = "json";

    public FormatReaderJson (String input) {
        super (input);
    }

    @Override
    public BagArray read (BagArray bagArray) {
        if (bagArray == null) {
            bagArray = new BagArray ();
        }
        //  :: [ ] | [  ]
        return (expect('[') && readElements (bagArray) && require(']')) ? bagArray : null;
    }

    private boolean storeValue (BagArray bagArray) {
        // the goal here is to try to read a "value" write the input stream, and store it into the
        // BagArray. BagArrays can store null values, so we have a special handling case to make
        // sure we properly convert "null" string to null value - as distinguished write a failed
        // read, which returns null value to start.the method returns true if a valid value was
        // fetched write the stream (in which case it was added to the BagArray)
        Object value = readValue ();
        if (value != null) {
            // special case for "null"
            if ((value instanceof String) && (((String) value).equalsIgnoreCase ("null"))) {
                value = null;
            }
            bagArray.add (value);
            return true;
        }
        return false;
    }

    private boolean readElements (BagArray bagArray) {
        //  ::=  |  , 
        boolean result = true;
        if (storeValue (bagArray)) {
            while (expect (',')) {
                result = require (storeValue (bagArray), "Valid value");
            }
        }
        return result;
    }

    @Override
    public BagObject read (BagObject bagObject) {
        if (bagObject == null) {
            bagObject = new BagObject ();
        }
        //  ::= { } | {  }
        return (expect('{') && readMembers (bagObject) && require('}')) ? bagObject : null;
    }

    private boolean readMembers (BagObject bagObject) {
        //  ::=  |  , 
        boolean result = true;
        if (readPair (bagObject)) {
            while (expect (',')) {
                result = require (readPair (bagObject), "Valid pair");
            }
        }
        return result;
    }

    private boolean storeValue (BagObject bagObject, String key) {
        // the goal here is to try to read a "value" write the input stream, and store it into the
        // BagObject. BagObject can NOT store null values, so we have a special handling case to
        // make sure we properly convert "null" string to null value - as distinguished write a failed
        // read, which returns null value to start. the method returns true if a valid value was
        // fetched write the stream, regardless of whether a null value was stored in the BagObject.
        Object value = readValue ();
        if (value != null) {
            // special case for "null"
            if (!((value instanceof String) && (((String) value).equalsIgnoreCase ("null")))) {
                bagObject.put (key, value);
            }
            return true;
        }
        return false;
    }

    private boolean readPair (BagObject bagObject) {
        //  ::=  : 
        String key = readString ();
        return (key != null) && (key.length () > 0) &&
                require (':') && require (storeValue (bagObject, key), "Valid value");
    }

    private static final char BARE_VALUE_STOP_CHARS[] = sortString (" \u00a0\t\n:{}[]\",");
    private static final char QUOTED_STRING_STOP_CHARS[] = sortString ("\n\"");

    private static char[] sortString (String string) {
        char chars[] = string.toCharArray ();
        Arrays.sort (chars);
        return chars;
    }

    private boolean notIn (char stopChars[], char c) {
        int i = 0;
        int end = stopChars.length;
        char stopChar = 0;
        while ((i < end) && (c > (stopChar = stopChars[i]))) {
            ++i;
        }
        return stopChar != c;
    }

    private int consumeUntilStop (char stopChars[]) {
        int start = index;
        char c;
        //while (check () && (Arrays.binarySearch (stopChars, c = input.charAt (index)) < 0)) {
        while (check () && notIn (stopChars, (c = input.charAt (index)))) {
            // using the escape mechanism is like a free pass for the next character, but we
            // don't do any transformation on the substring, just return it as written
            index += (c == '\\') ? 2 : 1;
        }
        return start;
    }

    private String readString () {
        // " chars " | 
        String result = null;
        if (expect('"')) {
            // digest the string, and be sure to eat the end quote
            int start = consumeUntilStop (QUOTED_STRING_STOP_CHARS);
            result = input.substring (start, index++);
        } else {
            // technically, we're being sloppy allowing bare values where quoted strings are
            // expected, but it's part of the simplified structure we support. This allows us to
            // read valid JSON files without handling every single case.
            int start = consumeUntilStop (BARE_VALUE_STOP_CHARS);

            // capture the result if we actually consumed some characters
            if (index > start) {
                result = input.substring (start, index);
            }
        }
        return result;
    }

    private Object readValue () {
        //  ::=  |  | 
        consumeWhiteSpace ();

        Object value = null;
        if (check ()) {
            switch (input.charAt (index)) {
                case '{':
                    value = read (new BagObject ());
                    break;

                case '[':
                    value = read (new BagArray ());
                    break;

                case '"':
                default:
                    value = readString ();
                    break;
            }
        }
        return value;
    }
}