groovy-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From pa...@apache.org
Subject [3/7] groovy git commit: GROOVY-8379: Rework groovy-json FastStringUtils (closes #667)
Date Sat, 19 May 2018 14:08:20 GMT
http://git-wip-us.apache.org/repos/asf/groovy/blob/25e2a386/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonFastParser.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonFastParser.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonFastParser.java
new file mode 100644
index 0000000..4cc219c
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonFastParser.java
@@ -0,0 +1,334 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.groovy.json.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This works in index overlay mode or chop mode.
+ * Chop mode reduces possibility of memory leak but causes a few more buffer copies as it chops up the buffer.
+ *
+ * @author Rick Hightower
+ */
+public class JsonFastParser extends JsonParserCharArray {
+
+    private final boolean useValues;
+    private final boolean chop;
+    private final boolean lazyChop;
+    private final boolean checkDates;
+
+    public JsonFastParser() {
+        this(true);
+    }
+
+    public JsonFastParser(boolean useValues) {
+        this(useValues, false);
+    }
+
+    public JsonFastParser(boolean useValues, boolean chop) {
+        this(useValues, chop, !chop);
+    }
+
+    public JsonFastParser(boolean useValues, boolean chop, boolean lazyChop) {
+        this(useValues, chop, lazyChop, true);
+    }
+
+    public JsonFastParser(boolean useValues, boolean chop, boolean lazyChop, boolean checkDates) {
+        this.useValues = useValues;
+        this.chop = chop;
+        this.lazyChop = lazyChop;
+        this.checkDates = checkDates;
+    }
+
+    protected final Value decodeJsonObjectLazyFinalParse() {
+        char[] array = charArray;
+
+        if (__currentChar == '{')
+            __index++;
+
+        ValueMap map = useValues ? new ValueMapImpl() : new LazyValueMap(lazyChop);
+        Value value = new ValueContainer(map);
+
+        objectLoop:
+        for (; __index < array.length; __index++) {
+            skipWhiteSpace();
+            switch (__currentChar) {
+
+                case '"':
+                    Value key = decodeStringOverlay();
+                    skipWhiteSpace();
+
+                    if (__currentChar != ':') {
+
+                        complain("expecting current character to be " + charDescription(__currentChar) + "\n");
+                    }
+                    __index++;
+
+                    Value item = decodeValueOverlay();
+
+                    skipWhiteSpace();
+
+                    MapItemValue miv = new MapItemValue(key, item);
+
+                    map.add(miv);
+            }
+
+            switch (__currentChar) {
+                case '}':
+                    __index++;
+                    break objectLoop;
+
+                case ',':
+                    continue;
+
+                default:
+
+                    complain(
+                            "expecting '}' or ',' but got current char " + charDescription(__currentChar));
+            }
+        }
+        return value;
+    }
+
+    protected Value decodeValue() {
+        return decodeValueOverlay();
+    }
+
+    private Value decodeValueOverlay() {
+        skipWhiteSpace();
+
+        switch (__currentChar) {
+            case '"':
+                return decodeStringOverlay();
+
+            case '{':
+                return decodeJsonObjectLazyFinalParse();
+
+            case 't':
+                return decodeTrue() ? ValueContainer.TRUE : ValueContainer.FALSE;
+
+            case 'f':
+                return !decodeFalse() ? ValueContainer.FALSE : ValueContainer.TRUE;
+
+            case 'n':
+                return decodeNull() == null ? ValueContainer.NULL : ValueContainer.NULL;
+
+            case '[':
+                return decodeJsonArrayOverlay();
+
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            case '0':
+                return decodeNumberOverlay(false);
+
+            case '-':
+                return decodeNumberOverlay(true);
+
+            default:
+                complain("Unable to determine the " +
+                        "current character, it is not a string, number, array, or object");
+                return null;
+        }
+    }
+
+    private Value decodeNumberOverlay(final boolean minus) {
+        char[] array = charArray;
+
+        final int startIndex = __index;
+        int index = __index;
+        char currentChar;
+        boolean doubleFloat = false;
+        boolean foundDot = false;
+        boolean foundSign = false;
+        boolean foundExp = false;
+
+        if (minus && index + 1 < array.length) {
+            index++;
+        }
+
+        while (true) {
+            currentChar = array[index];
+            if (isNumberDigit(currentChar)) {
+                //noop
+            } else if (currentChar <= 32) { //white
+                break;
+            } else if (isDelimiter(currentChar)) {
+                break;
+            } else if (isDecimalChar(currentChar)) {
+                switch (currentChar) {
+                    case DECIMAL_POINT:
+                        if (foundDot || foundExp) { complain("unexpected character " + currentChar); }
+                        foundDot = true;
+                        break;
+                    case LETTER_E:
+                    case LETTER_BIG_E:
+                        if (foundExp) { complain("unexpected character " + currentChar); }
+                        foundExp = true;
+                        break;
+                    case MINUS:
+                    case PLUS:
+                        if (foundSign || !foundExp) { complain("unexpected character " + currentChar); }
+                        if (foundExp && array[index - 1] != LETTER_E && array[index - 1] != LETTER_BIG_E) {
+                            complain("unexpected character " + currentChar);
+                        }
+                        foundSign = true;
+                        break;
+                }
+                doubleFloat = true;
+            } else {
+                complain("unexpected character " + currentChar);
+            }
+            index++;
+            if (index >= array.length) break;
+        }
+
+        // Handle the case where the exponential number ends without the actual exponent
+        if (foundExp) {
+            char prevChar = array[index - 1];
+            if (prevChar == LETTER_E || prevChar == LETTER_BIG_E || prevChar == MINUS || prevChar == PLUS) {
+                complain("unexpected character " + currentChar);
+            }
+        }
+
+        __index = index;
+        __currentChar = currentChar;
+
+        Type type = doubleFloat ? Type.DOUBLE : Type.INTEGER;
+
+        return new NumberValue(chop, type, startIndex, __index, this.charArray);
+    }
+
+    private Value decodeStringOverlay() {
+        char[] array = charArray;
+        int index = __index;
+        char currentChar = charArray[index];
+
+        if (index < array.length && currentChar == '"') {
+            index++;
+        }
+
+        final int startIndex = index;
+
+        boolean encoded = hasEscapeChar(array, index, indexHolder);
+        index = indexHolder[0];
+
+        if (encoded) {
+            index = findEndQuote(array, index);
+        }
+
+        Value value = new CharSequenceValue(chop, Type.STRING, startIndex, index, array, encoded, checkDates);
+
+        if (index < array.length) {
+            index++;
+        }
+
+        __index = index;
+        return value;
+    }
+
+    private Value decodeJsonArrayOverlay() {
+        char[] array = charArray;
+        if (__currentChar == '[') {
+            __index++;
+        }
+
+        skipWhiteSpace();
+
+        /* the list might be empty  */
+        if (__currentChar == ']') {
+            __index++;
+            return new ValueContainer(new ArrayList());
+        }
+
+        List<Object> list;
+
+        if (useValues) {
+            list = new ArrayList<Object>();
+        } else {
+            list = new ValueList(lazyChop);
+        }
+
+        Value value = new ValueContainer(list);
+
+        Value item;
+        char c;
+        int lastIndex;
+        boolean foundEnd = false;
+
+        arrayLoop:
+        for (; __index < array.length; __index++) {
+            item = decodeValueOverlay();
+
+            list.add(item);
+            c = currentChar();
+            switch (c) {
+                case ',':
+                    continue;
+                case ']':
+                    __index++;
+                    foundEnd = true;
+                    break arrayLoop;
+            }
+
+            lastIndex = __index;
+            skipWhiteSpace();
+            c = currentChar();
+
+            switch (c) {
+                case ',':
+                    continue;
+                case ']':
+                    if (__index == lastIndex) {
+                        complain("missing ]");
+                    }
+                    foundEnd = true;
+                    __index++;
+                    break arrayLoop;
+                default:
+                    complain(
+                            String.format("expecting a ',' or a ']', " +
+                                    " but got \nthe current character of  %s " +
+                                    " on array size of %s \n", charDescription(__currentChar), list.size())
+                    );
+            }
+        }
+
+        if (!foundEnd) {
+            complain("Did not find end of Json Array");
+        }
+        return value;
+    }
+
+    protected final Object decodeFromChars(char[] cs) {
+        Value value = ((Value) super.decodeFromChars(cs));
+        if (value.isContainer()) {
+            return value.toValue();
+        } else {
+            return value;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/25e2a386/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserCharArray.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserCharArray.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserCharArray.java
new file mode 100644
index 0000000..6fb6f4c
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserCharArray.java
@@ -0,0 +1,386 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.groovy.json.internal;
+
+import groovy.json.JsonException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Converts an input JSON String into Java objects works with String or char array
+ * as input. Produces an Object which can be any of the basic JSON types mapped
+ * to Java.
+ * <p/>
+ *
+ * @author Rick Hightower
+ */
+public class JsonParserCharArray extends BaseJsonParser {
+
+    protected char[] charArray;
+    protected int __index;
+    protected char __currentChar;
+
+    private int lastIndex;
+
+    protected Object decodeFromChars(char[] cs) {
+        __index = 0;
+        charArray = cs;
+        lastIndex = cs.length - 1;
+        return decodeValue();
+    }
+
+    protected final boolean hasMore() {
+        return __index < lastIndex;
+    }
+
+    protected final boolean hasCurrent() {
+        return __index <= lastIndex;
+    }
+
+    protected final void skipWhiteSpace() {
+        int ix = __index;
+
+        if (hasCurrent()) {
+            this.__currentChar = this.charArray[ix];
+        }
+
+        if (__currentChar <= 32) {
+            ix = skipWhiteSpaceFast(this.charArray, ix);
+            this.__currentChar = this.charArray[ix];
+            __index = ix;
+        }
+    }
+
+    protected final char nextChar() {
+        try {
+            if (hasMore()) {
+                __index++;
+                return __currentChar = charArray[__index];
+            } else {
+                // TODO move unicode 0 to separate file to avoid doc parsing issues
+                return '\u0000';
+            }
+        } catch (Exception ex) {
+            throw new JsonException(exceptionDetails("unable to advance character"), ex);
+        }
+    }
+
+    protected String exceptionDetails(String message) {
+        return CharScanner.errorDetails(message, charArray, __index, __currentChar);
+    }
+
+    private static int skipWhiteSpaceFast(char[] array, int index) {
+        char c;
+        for (; index < array.length; index++) {
+            c = array[index];
+            if (c > 32) {
+                return index;
+            }
+        }
+        return index - 1;
+    }
+
+    protected final Object decodeJsonObject() {
+        if (__currentChar == '{') {
+            __index++;
+        }
+
+        LazyMap map = new LazyMap();
+
+        for (; __index < this.charArray.length; __index++) {
+            skipWhiteSpace();
+
+            if (__currentChar == '"') {
+                String key = decodeString();
+
+                if (internKeys) {
+                    String keyPrime = internedKeysCache.get(key);
+                    if (keyPrime == null) {
+                        key = key.intern();
+                        internedKeysCache.put(key, key);
+                    } else {
+                        key = keyPrime;
+                    }
+                }
+
+                skipWhiteSpace();
+
+                if (__currentChar != ':') {
+                    complain("expecting current character to be " + charDescription(__currentChar) + "\n");
+                }
+                __index++;
+
+                skipWhiteSpace();
+
+                Object value = decodeValueInternal();
+
+                skipWhiteSpace();
+                map.put(key, value);
+            }
+
+            if (__currentChar == '}') {
+                __index++;
+                break;
+            } else if (__currentChar == ',') {
+                continue;
+            } else {
+                complain(
+                        "expecting '}' or ',' but got current char " + charDescription(__currentChar));
+            }
+        }
+
+        return map;
+    }
+
+    protected final void complain(String complaint) {
+        throw new JsonException(exceptionDetails(complaint));
+    }
+
+    protected Object decodeValue() {
+        return decodeValueInternal();
+    }
+
+    private Object decodeValueInternal() {
+        Object value = null;
+        skipWhiteSpace();
+
+        switch (__currentChar) {
+            case '"':
+                value = decodeString();
+                break;
+
+            case 't':
+                value = decodeTrue();
+                break;
+
+            case 'f':
+                value = decodeFalse();
+                break;
+
+            case 'n':
+                value = decodeNull();
+                break;
+
+            case '[':
+                value = decodeJsonArray();
+                break;
+
+            case '{':
+                value = decodeJsonObject();
+                break;
+
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                value = decodeNumber();
+                break;
+            case '-':
+                value = decodeNumber();
+                break;
+
+            default:
+                throw new JsonException(exceptionDetails("Unable to determine the " +
+                        "current character, it is not a string, number, array, or object"));
+        }
+
+        return value;
+    }
+
+    int[] endIndex = new int[1];
+
+    private Object decodeNumber() {
+        Number num = CharScanner.parseJsonNumber(charArray, __index, charArray.length, endIndex);
+        __index = endIndex[0];
+
+        return num;
+    }
+
+    protected static final char[] NULL = Chr.chars("null");
+
+    protected final Object decodeNull() {
+        if (__index + NULL.length <= charArray.length) {
+            if (charArray[__index] == 'n' &&
+                    charArray[++__index] == 'u' &&
+                    charArray[++__index] == 'l' &&
+                    charArray[++__index] == 'l') {
+                __index++;
+                return null;
+            }
+        }
+        throw new JsonException(exceptionDetails("null not parse properly"));
+    }
+
+    protected static final char[] TRUE = Chr.chars("true");
+
+    protected final boolean decodeTrue() {
+        if (__index + TRUE.length <= charArray.length) {
+            if (charArray[__index] == 't' &&
+                    charArray[++__index] == 'r' &&
+                    charArray[++__index] == 'u' &&
+                    charArray[++__index] == 'e') {
+
+                __index++;
+                return true;
+            }
+        }
+
+        throw new JsonException(exceptionDetails("true not parsed properly"));
+    }
+
+    protected static char[] FALSE = Chr.chars("false");
+
+    protected final boolean decodeFalse() {
+        if (__index + FALSE.length <= charArray.length) {
+            if (charArray[__index] == 'f' &&
+                    charArray[++__index] == 'a' &&
+                    charArray[++__index] == 'l' &&
+                    charArray[++__index] == 's' &&
+                    charArray[++__index] == 'e') {
+                __index++;
+                return false;
+            }
+        }
+        throw new JsonException(exceptionDetails("false not parsed properly"));
+    }
+
+    private CharBuf builder = CharBuf.create(20);
+
+    private String decodeString() {
+        char[] array = charArray;
+        int index = __index;
+        char currentChar = array[index];
+
+        if (index < array.length && currentChar == '"') {
+            index++;
+        }
+
+        final int startIndex = index;
+
+        boolean encoded = hasEscapeChar(array, index, indexHolder);
+        index = indexHolder[0];
+
+        String value = null;
+        if (encoded) {
+            index = findEndQuote(array, index);
+            value = builder.decodeJsonString(array, startIndex, index).toString();
+            builder.recycle();
+        } else {
+            value = new String(array, startIndex, (index - startIndex));
+        }
+
+        if (index < charArray.length) {
+            index++;
+        }
+        __index = index;
+        return value;
+    }
+
+    protected final List decodeJsonArray() {
+        ArrayList<Object> list = null;
+
+        boolean foundEnd = false;
+        char[] charArray = this.charArray;
+
+        try {
+            if (__currentChar == '[') {
+                __index++;
+            }
+
+            int lastIndex;
+
+            skipWhiteSpace();
+
+        /* the list might be empty  */
+            if (__currentChar == ']') {
+                __index++;
+                return new ArrayList();
+            }
+
+            list = new ArrayList();
+
+            while (this.hasMore()) {
+                Object arrayItem = decodeValueInternal();
+
+                list.add(arrayItem);
+
+                char c = charArray[__index];
+
+                if (c == ',') {
+                    __index++;
+                    continue;
+                } else if (c == ']') {
+                    __index++;
+                    foundEnd = true;
+                    break;
+                }
+
+                lastIndex = __index;
+                skipWhiteSpace();
+
+                c = charArray[__index];
+
+                if (c == ',') {
+                    __index++;
+                    continue;
+                } else if (c == ']' && lastIndex != __index) {
+                    __index++;
+                    foundEnd = true;
+                    break;
+                } else {
+                    String charString = charDescription(c);
+
+                    complain(
+                            String.format("expecting a ',' or a ']', " +
+                                    " but got \nthe current character of  %s " +
+                                    " on array index of %s \n", charString, list.size())
+                    );
+                }
+            }
+        } catch (Exception ex) {
+            if (ex instanceof JsonException) {
+                throw (JsonException) ex;
+            }
+            throw new JsonException(exceptionDetails("issue parsing JSON array"), ex);
+        }
+        if (!foundEnd) {
+            complain("Did not find end of Json Array");
+        }
+        return list;
+    }
+
+    protected final char currentChar() {
+        if (__index > lastIndex) {
+            return 0;
+        } else {
+            return charArray[__index];
+        }
+    }
+
+    public Object parse(char[] chars) {
+        return this.decodeFromChars(chars);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/25e2a386/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserLax.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserLax.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserLax.java
new file mode 100644
index 0000000..89e1fa4
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserLax.java
@@ -0,0 +1,677 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.groovy.json.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Richard Hightower
+ */
+public class JsonParserLax extends JsonParserCharArray {
+
+    private final boolean useValues;
+    private final boolean chop;
+    private final boolean lazyChop;
+    private final boolean defaultCheckDates;
+
+    public JsonParserLax() {
+        this(true);
+    }
+
+    public JsonParserLax(boolean useValues) {
+        this(useValues, false);
+    }
+
+    public JsonParserLax(boolean useValues, boolean chop) {
+        this(useValues, chop, !chop);
+    }
+
+    public JsonParserLax(boolean useValues, boolean chop, boolean lazyChop) {
+        this(useValues, chop, lazyChop, true);
+    }
+
+    public JsonParserLax(boolean useValues, boolean chop, boolean lazyChop, boolean defaultCheckDates) {
+        this.useValues = useValues;
+        this.chop = chop;
+        this.lazyChop = lazyChop;
+        this.defaultCheckDates = defaultCheckDates;
+    }
+
+    private Value decodeJsonObjectLax() {
+        if (__currentChar == '{')
+            this.nextChar();
+
+        ValueMap map = useValues ? new ValueMapImpl() : new LazyValueMap(lazyChop);
+        Value value = new ValueContainer(map);
+
+        skipWhiteSpace();
+        int startIndexOfKey = __index;
+        Value key;
+        MapItemValue miv;
+        Value item;
+
+        done:
+        for (; __index < this.charArray.length; __index++) {
+            skipWhiteSpace();
+
+            switch (__currentChar) {
+                case ':':
+                    char startChar = charArray[startIndexOfKey];
+                    if (startChar == ',') {
+                        startIndexOfKey++;
+                    }
+
+                    key = extractLaxString(startIndexOfKey, __index - 1, false, false);
+                    __index++; //skip :
+
+                    item = decodeValueInternal();
+                    skipWhiteSpace();
+
+                    miv = new MapItemValue(key, item);
+
+                    map.add(miv);
+
+                    startIndexOfKey = __index;
+                    if (__currentChar == '}') {
+                        __index++;
+                        break done;
+                    }
+
+                    break;
+
+                case '\'':
+                    key = decodeStringSingle();
+
+                    //puts ("key with quote", key);
+
+                    skipWhiteSpace();
+
+                    if (__currentChar != ':') {
+                        complain("expecting current character to be ':' but got " + charDescription(__currentChar) + "\n");
+                    }
+                    __index++;
+                    item = decodeValueInternal();
+
+                    //puts ("key", "#" + key + "#", value);
+
+                    skipWhiteSpace();
+
+                    miv = new MapItemValue(key, item);
+
+                    map.add(miv);
+                    startIndexOfKey = __index;
+                    if (__currentChar == '}') {
+                        __index++;
+                        break done;
+                    }
+
+                    break;
+
+                case '"':
+                    key = decodeStringDouble();
+
+                    //puts ("key with quote", key);
+
+                    skipWhiteSpace();
+
+                    if (__currentChar != ':') {
+                        complain("expecting current character to be ':' but got " + charDescription(__currentChar) + "\n");
+                    }
+                    __index++;
+                    item = decodeValueInternal();
+
+                    //puts ("key", "#" + key + "#", value);
+
+                    skipWhiteSpace();
+
+                    miv = new MapItemValue(key, item);
+
+                    map.add(miv);
+                    startIndexOfKey = __index;
+                    if (__currentChar == '}') {
+                        __index++;
+                        break done;
+                    }
+
+                    break;
+            }
+
+            switch (__currentChar) {
+                case '}':
+                    __index++;
+                    break done;
+
+                case '/': /* */ //
+                    handleComment();
+                    startIndexOfKey = __index;
+                    break;
+
+                case '#':
+                    handleBashComment();
+                    startIndexOfKey = __index;
+                    break;
+            }
+        }
+
+        return value;
+    }
+
+    private Value extractLaxString(int startIndexOfKey, int end, boolean encoded, boolean checkDate) {
+        char startChar;
+        startIndexLookup:
+        for (; startIndexOfKey < __index && startIndexOfKey < charArray.length; startIndexOfKey++) {
+            startChar = charArray[startIndexOfKey];
+            switch (startChar) {
+                case ' ':
+                case '\n':
+                case '\t':
+                    continue;
+
+                default:
+                    break startIndexLookup;
+            }
+        }
+
+        char endChar;
+        int endIndex = end >= charArray.length ? charArray.length - 1 : end;
+        endIndexLookup:
+        for (; endIndex >= startIndexOfKey + 1 && endIndex >= 0; endIndex--) {
+            endChar = charArray[endIndex];
+            switch (endChar) {
+                case ' ':
+                case '\n':
+                case '\t':
+                case '}':
+                    continue;
+                case ',':
+                case ';':
+                    continue;
+
+                case ']':
+                    continue;
+                default:
+                    break endIndexLookup;
+            }
+        }
+        return new CharSequenceValue(chop, Type.STRING, startIndexOfKey, endIndex + 1, this.charArray, encoded, checkDate);
+    }
+
+    protected final Object decodeValue() {
+        return this.decodeValueInternal();
+    }
+
+    private Value decodeValueInternal() {
+        Value value = null;
+
+        for (; __index < charArray.length; __index++) {
+            skipWhiteSpace();
+
+            switch (__currentChar) {
+                case '\n':
+                    break;
+
+                case '\r':
+                    break;
+
+                case ' ':
+                    break;
+
+                case '\t':
+                    break;
+
+                case '\b':
+                    break;
+
+                case '\f':
+                    break;
+
+                case '/': /* */ //
+                    handleComment();
+                    break;
+
+                case '#':
+                    handleBashComment();
+                    break;
+
+                case '"':
+                    value = decodeStringDouble();
+                    break;
+
+                case '\'':
+                    value = decodeStringSingle();
+                    break;
+
+                case 't':
+                    if (isTrue()) {
+                        return decodeTrue() ? ValueContainer.TRUE : ValueContainer.FALSE;
+                    } else {
+                        value = decodeStringLax();
+                    }
+                    break;
+
+                case 'f':
+                    if (isFalse()) {
+                        return !decodeFalse() ? ValueContainer.FALSE : ValueContainer.TRUE;
+                    } else {
+                        value = decodeStringLax();
+                    }
+                    break;
+
+                case 'n':
+                    if (isNull()) {
+                        return decodeNull() == null ? ValueContainer.NULL : ValueContainer.NULL;
+                    } else {
+                        value = decodeStringLax();
+                    }
+
+                    break;
+
+                case '[':
+                    value = decodeJsonArrayLax();
+                    break;
+
+                case '{':
+                    value = decodeJsonObjectLax();
+                    break;
+
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                case '8':
+                case '9':
+                case '0':
+                    return decodeNumberLax(false);
+
+                case '-':
+                    return decodeNumberLax(true);
+
+                default:
+                    value = decodeStringLax();
+            }
+
+            if (value != null) {
+                return value;
+            }
+        }
+
+        return null;
+    }
+
+    private void handleBashComment() {
+        for (; __index < charArray.length; __index++) {
+            __currentChar = charArray[__index];
+
+            if (__currentChar == '\n') {
+                __index++;
+                return;
+            }
+        }
+    }
+
+    private void handleComment() {
+        if (hasMore()) {
+            __index++;
+            __currentChar = charArray[__index];
+
+            switch (__currentChar) {
+                case '*':
+                    for (; __index < charArray.length; __index++) {
+                        __currentChar = charArray[__index];
+
+                        if (__currentChar == '*') {
+                            if (hasMore()) {
+                                __index++;
+                                __currentChar = charArray[__index];
+                                if (__currentChar == '/') {
+                                    if (hasMore()) {
+                                        __index++;
+                                        return;
+                                    }
+                                }
+                            } else {
+                                complain("missing close of comment");
+                            }
+                        }
+                    }
+
+                case '/':
+                    for (; __index < charArray.length; __index++) {
+                        __currentChar = charArray[__index];
+
+                        if (__currentChar == '\n') {
+                            if (hasMore()) {
+                                __index++;
+                                return;
+                            } else {
+                                return;
+                            }
+                        }
+                    }
+            }
+        }
+    }
+
+    /**
+     * Decodes a number from a JSON value.  If at any point it is determined that
+     * the value is not a valid number the value is treated as a {@code String}.
+     *
+     * @param minus indicate whether the number is negative
+     * @return a number, or {@code String} if not a valid number
+     */
+    protected final Value decodeNumberLax(boolean minus) {
+        char[] array = charArray;
+
+        final int startIndex = __index;
+        int index = __index;
+        char currentChar;
+        boolean doubleFloat = false;
+        boolean foundDot = false;
+        boolean foundSign = false;
+        boolean foundExp = false;
+
+        if (minus && index + 1 < array.length) {
+            index++;
+        }
+
+        while (true) {
+            currentChar = array[index];
+            if (isNumberDigit(currentChar)) {
+                //noop
+            } else if (currentChar <= 32) { //white
+                break;
+            } else if (isDelimiter(currentChar)) {
+                break;
+            } else if (isDecimalChar(currentChar)) {
+                switch (currentChar) {
+                    case DECIMAL_POINT:
+                        if (foundDot || foundExp) { return decodeStringLax(); }
+                        foundDot = true;
+                        break;
+                    case LETTER_E:
+                    case LETTER_BIG_E:
+                        if (foundExp) { return decodeStringLax(); }
+                        foundExp = true;
+                        break;
+                    case MINUS:
+                    case PLUS:
+                        if (foundSign || !foundExp) { return decodeStringLax(); }
+                        if (foundExp && array[index - 1] != LETTER_E && array[index - 1] != LETTER_BIG_E) {
+                            return decodeStringLax();
+                        }
+                        foundSign = true;
+                        break;
+                }
+                doubleFloat = true;
+            } else {
+                return decodeStringLax();
+            }
+            index++;
+            if (index >= array.length) break;
+        }
+
+        // Handle the case where the exponential number ends without the actual exponent
+        if (foundExp) {
+            char prevChar = array[index - 1];
+            if (prevChar == LETTER_E || prevChar == LETTER_BIG_E || prevChar == MINUS || prevChar == PLUS) {
+                return decodeStringLax();
+            }
+        }
+
+        __index = index;
+        __currentChar = currentChar;
+
+        Type type = doubleFloat ? Type.DOUBLE : Type.INTEGER;
+
+        return new NumberValue(chop, type, startIndex, __index, this.charArray);
+    }
+
+    private boolean isNull() {
+        if (__index + NULL.length <= charArray.length) {
+            if (charArray[__index] == 'n' &&
+                    charArray[__index + 1] == 'u' &&
+                    charArray[__index + 2] == 'l' &&
+                    charArray[__index + 3] == 'l') {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isTrue() {
+        if (__index + TRUE.length <= charArray.length) {
+            if (charArray[__index] == 't' &&
+                    charArray[__index + 1] == 'r' &&
+                    charArray[__index + 2] == 'u' &&
+                    charArray[__index + 3] == 'e') {
+                return true;
+
+            }
+        }
+        return false;
+    }
+
+    private boolean isFalse() {
+        if (__index + FALSE.length <= charArray.length) {
+            if (charArray[__index] == 'f' &&
+                    charArray[__index + 1] == 'a' &&
+                    charArray[__index + 2] == 'l' &&
+                    charArray[__index + 3] == 's' &&
+                    charArray[__index + 4] == 'e') {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private Value decodeStringLax() {
+        int index = __index;
+        char currentChar = charArray[__index];
+        final int startIndex = __index;
+        boolean encoded = false;
+        char[] charArray = this.charArray;
+
+        for (; index < charArray.length; index++) {
+            currentChar = charArray[index];
+
+            if (isDelimiter(currentChar)) break;
+            else if (currentChar == '\\') break;
+        }
+
+        Value value = this.extractLaxString(startIndex, index, encoded, defaultCheckDates);
+
+        __index = index;
+        return value;
+    }
+
+    private Value decodeStringDouble() {
+        __currentChar = charArray[__index];
+
+        if (__index < charArray.length && __currentChar == '"') {
+            __index++;
+        }
+
+        final int startIndex = __index;
+
+        boolean escape = false;
+        boolean encoded = false;
+
+        done:
+        for (; __index < this.charArray.length; __index++) {
+            __currentChar = charArray[__index];
+            switch (__currentChar) {
+
+                case '"':
+                    if (!escape) {
+                        break done;
+                    } else {
+                        escape = false;
+                        continue;
+                    }
+
+                case '\\':
+                    escape = !escape;
+                    encoded = true;
+                    continue;
+            }
+            escape = false;
+        }
+
+        Value value = new CharSequenceValue(chop, Type.STRING, startIndex, __index, this.charArray, encoded, defaultCheckDates);
+
+        if (__index < charArray.length) {
+            __index++;
+        }
+
+        return value;
+    }
+
+    private Value decodeStringSingle() {
+        __currentChar = charArray[__index];
+
+        if (__index < charArray.length && __currentChar == '\'') {
+            __index++;
+        }
+
+        final int startIndex = __index;
+
+        boolean escape = false;
+        boolean encoded = false;
+        int minusCount = 0;
+        int colonCount = 0;
+
+        done:
+        for (; __index < this.charArray.length; __index++) {
+            __currentChar = charArray[__index];
+            switch (__currentChar) {
+                case '\'':
+                    if (!escape) {
+                        break done;
+                    } else {
+                        escape = false;
+                        continue;
+                    }
+
+                case '\\':
+                    encoded = true;
+                    escape = true;
+                    continue;
+
+                case '-':
+                    minusCount++;
+                    break;
+
+                case ':':
+                    colonCount++;
+                    break;
+            }
+            escape = false;
+        }
+
+        boolean checkDates = defaultCheckDates && !encoded && minusCount >= 2 && colonCount >= 2;
+
+        Value value = new CharSequenceValue(chop, Type.STRING, startIndex, __index, this.charArray, encoded, checkDates);
+
+        if (__index < charArray.length) {
+            __index++;
+        }
+
+        return value;
+    }
+
+    private Value decodeJsonArrayLax() {
+        if (__currentChar == '[') {
+            __index++;
+        }
+
+        skipWhiteSpace();
+
+        if (__currentChar == ']') {
+            __index++;
+            return new ValueContainer(new ArrayList());
+        }
+
+        List<Object> list;
+
+        if (useValues) {
+            list = new ArrayList<Object>();
+        } else {
+            list = new ValueList(lazyChop);
+        }
+
+        Value value = new ValueContainer(list);
+
+        do {
+            skipWhiteSpace();
+
+            Object arrayItem = decodeValueInternal();
+
+            list.add(arrayItem);
+
+            boolean doStop = false;
+
+            done:
+            do { // Find either next array element or end of array while ignoring comments
+                skipWhiteSpace();
+
+                switch (__currentChar) {
+                    case '/':
+                        handleComment();
+                        continue;
+                    case '#':
+                        handleBashComment();
+                        continue;
+                    case ',':
+                        __index++;
+                        break done;
+                    case ']':
+                        __index++;
+                        doStop = true;
+                        break done;
+                    default:
+                        String charString = charDescription(__currentChar);
+
+                        complain(
+                                String.format("expecting a ',' or a ']', " +
+                                        " but got \nthe current character of  %s " +
+                                        " on array index of %s \n", charString, list.size())
+                        );
+                }
+            } while (this.hasMore());
+
+            if (doStop) break;
+
+        } while (this.hasMore());
+
+        return value;
+    }
+
+    protected final Object decodeFromChars(char[] cs) {
+        Value value = ((Value) super.decodeFromChars(cs));
+        if (value.isContainer()) {
+            return value.toValue();
+        } else {
+            return value;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/25e2a386/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserUsingCharacterSource.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserUsingCharacterSource.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserUsingCharacterSource.java
new file mode 100644
index 0000000..8b7d969
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonParserUsingCharacterSource.java
@@ -0,0 +1,298 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.groovy.json.internal;
+
+import groovy.json.JsonException;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Converts an input JSON String into Java objects works with String or char array
+ * as input. Produces an Object which can be any of the basic JSON types mapped
+ * to Java.
+ * <p/>
+ *
+ * @author Rick Hightower
+ */
+public class JsonParserUsingCharacterSource extends BaseJsonParser {
+
+    private CharacterSource characterSource;
+
+    protected String exceptionDetails(String message) {
+        return characterSource.errorDetails(message);
+    }
+
+    protected final Object decodeJsonObject() {
+        LazyMap map = new LazyMap();
+
+        try {
+            CharacterSource characterSource = this.characterSource;
+
+            if (characterSource.currentChar() == '{') {
+                characterSource.nextChar();
+            }
+
+            while (characterSource.hasChar()) {
+                characterSource.skipWhiteSpace();
+
+                if (characterSource.currentChar() == DOUBLE_QUOTE) {
+                    String key = decodeString();
+                    //puts ("key", key);
+
+                    if (internKeys) {
+                        String keyPrime = internedKeysCache.get(key);
+                        if (keyPrime == null) {
+                            key = key.intern();
+                            internedKeysCache.put(key, key);
+                        } else {
+                            key = keyPrime;
+                        }
+                    }
+
+                    characterSource.skipWhiteSpace();
+                    if (characterSource.currentChar() != COLON) {
+                        complain("expecting current character to be : but was " + charDescription(characterSource.currentChar()) + "\n");
+                    }
+
+                    characterSource.nextChar();
+                    characterSource.skipWhiteSpace();
+
+                    Object value = decodeValue();
+
+                    //puts ("key", key, "value", value);
+
+                    characterSource.skipWhiteSpace();
+
+                    map.put(key, value);
+                }
+
+                int ch = characterSource.currentChar();
+                if (ch == '}') {
+                    characterSource.nextChar();
+                    break;
+                } else if (ch == ',') {
+                    characterSource.nextChar();
+                    continue;
+                } else {
+                    complain(
+                            "expecting '}' or ',' but got current char " + charDescription(ch));
+                }
+            }
+        } catch (Exception ex) {
+            throw new JsonException(exceptionDetails("Unable to parse JSON object"), ex);
+        }
+
+        return map;
+    }
+
+    protected final void complain(String complaint) {
+        throw new JsonException(exceptionDetails(complaint));
+    }
+
+    private Object decodeValue() {
+        CharacterSource characterSource = this.characterSource;
+        Object value = null;
+        characterSource.skipWhiteSpace();
+
+        switch (characterSource.currentChar()) {
+            case '"':
+                value = decodeString();
+                break;
+
+            case 't':
+                value = decodeTrue();
+                break;
+
+            case 'f':
+                value = decodeFalse();
+                break;
+
+            case 'n':
+                value = decodeNull();
+                break;
+
+            case '[':
+                value = decodeJsonArray();
+                break;
+
+            case '{':
+                value = decodeJsonObject();
+                break;
+
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                value = decodeNumber(false);
+                break;
+
+            case '-':
+                value = decodeNumber(true);
+                break;
+
+            default:
+                throw new JsonException(exceptionDetails("Unable to determine the " +
+                        "current character, it is not a string, number, array, or object"));
+        }
+
+        return value;
+    }
+
+    private Object decodeNumber(boolean negative) {
+        char[] chars = characterSource.readNumber();
+        Object value = null;
+
+        if (CharScanner.hasDecimalChar(chars, negative)) {
+            value = CharScanner.parseBigDecimal(chars);
+        } else if (CharScanner.isInteger(chars)) {
+            value = CharScanner.parseInt(chars);
+        } else if (CharScanner.isLong(chars)) {
+            value = CharScanner.parseLong(chars);
+        }
+
+        return value;
+    }
+
+    protected static final char[] NULL = Chr.chars("null");
+
+    protected final Object decodeNull() {
+        if (!characterSource.consumeIfMatch(NULL)) {
+            throw new JsonException(exceptionDetails("null not parse properly"));
+        }
+        return null;
+    }
+
+    protected static final char[] TRUE = Chr.chars("true");
+
+    protected final boolean decodeTrue() {
+        if (characterSource.consumeIfMatch(TRUE)) {
+            return true;
+        } else {
+            throw new JsonException(exceptionDetails("true not parsed properly"));
+        }
+    }
+
+    protected static char[] FALSE = Chr.chars("false");
+
+    protected final boolean decodeFalse() {
+        if (characterSource.consumeIfMatch(FALSE)) {
+            return false;
+        } else {
+            throw new JsonException(exceptionDetails("false not parsed properly"));
+        }
+    }
+
+    private CharBuf builder = CharBuf.create(20);
+
+    private String decodeString() {
+        CharacterSource characterSource = this.characterSource;
+
+        characterSource.nextChar();
+
+        char[] chars = characterSource.findNextChar('"', '\\');
+
+        String value = null;
+        if (characterSource.hadEscape()) {
+            value = builder.decodeJsonString(chars).toString();
+            builder.recycle();
+        } else {
+            value = new String(chars);
+        }
+
+        return value;
+    }
+
+    protected final List decodeJsonArray() {
+        ArrayList<Object> list = null;
+
+        boolean foundEnd = false;
+        try {
+            CharacterSource characterSource = this.characterSource;
+
+            if (this.characterSource.currentChar() == '[') {
+                characterSource.nextChar();
+            }
+
+            characterSource.skipWhiteSpace();
+
+        /* the list might be empty  */
+            if (this.characterSource.currentChar() == ']') {
+                characterSource.nextChar();
+                return new ArrayList();
+            }
+
+            list = new ArrayList();
+
+            do {
+                characterSource.skipWhiteSpace();
+
+                Object arrayItem = decodeValue();
+
+                list.add(arrayItem);
+
+                characterSource.skipWhiteSpace();
+
+                int c = characterSource.currentChar();
+
+                if (c == COMMA) {
+                    characterSource.nextChar();
+                    continue;
+                } else if (c == CLOSED_BRACKET) {
+                    foundEnd = true;
+                    characterSource.nextChar();
+                    break;
+                } else {
+                    String charString = charDescription(c);
+
+                    complain(
+                            String.format("expecting a ',' or a ']', " +
+                                    " but got \nthe current character of  %s " +
+                                    " on array index of %s \n", charString, list.size())
+                    );
+
+                }
+            } while (characterSource.hasChar());
+        } catch (Exception ex) {
+            throw new JsonException(exceptionDetails("Unexpected issue"), ex);
+        }
+
+        if (!foundEnd) {
+            throw new JsonException(exceptionDetails("Could not find end of JSON array"));
+        }
+        return list;
+    }
+
+    public Object parse(Reader reader) {
+        characterSource = new ReaderCharacterSource(reader);
+        return this.decodeValue();
+    }
+
+    public Object parse(char[] chars) {
+        return parse(new StringReader(new String(chars)));
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/25e2a386/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonStringDecoder.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonStringDecoder.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonStringDecoder.java
new file mode 100644
index 0000000..a53c9fa
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/JsonStringDecoder.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.groovy.json.internal;
+
+/**
+ * @author Richard Hightower
+ */
+public class JsonStringDecoder {
+
+    public static String decode(char[] chars, int start, int to) {
+        if (!Chr.contains(chars, '\\', start, to - start)) {
+            return new String(chars, start, to - start);
+        }
+        return decodeForSure(chars, start, to);
+    }
+
+    public static String decodeForSure(char[] chars, int start, int to) {
+        CharBuf builder = CharBuf.create(to - start);
+        builder.decodeJsonString(chars, start, to);
+        return builder.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/25e2a386/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/LazyMap.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/LazyMap.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/LazyMap.java
new file mode 100644
index 0000000..81da4d3
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/LazyMap.java
@@ -0,0 +1,205 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.groovy.json.internal;
+
+import java.lang.reflect.Array;
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * This maps only builds once you ask for a key for the first time.
+ * It is designed to not incur the overhead of creating a map unless needed.
+ *
+ * @author Rick Hightower
+ */
+public class LazyMap extends AbstractMap<String, Object> {
+
+    static final String JDK_MAP_ALTHASHING_SYSPROP = System.getProperty("jdk.map.althashing.threshold");
+
+    /* Holds the actual map that will be lazily created. */
+    private Map<String, Object> map;
+    /* The size of the map. */
+    private int size;
+    /* The keys  stored in the map. */
+    private String[] keys;
+    /* The values stored in the map. */
+    private Object[] values;
+
+    public LazyMap() {
+        keys = new String[5];
+        values = new Object[5];
+    }
+
+    public LazyMap(int initialSize) {
+        keys = new String[initialSize];
+        values = new Object[initialSize];
+    }
+
+    public Object put(String key, Object value) {
+        if (map == null) {
+            for (int i = 0; i < size; i++) {
+                String curKey = keys[i];
+                if ((key == null && curKey == null)
+                     || (key != null && key.equals(curKey))) {
+                    Object val = values[i];
+                    keys[i] = key;
+                    values[i] = value;
+                    return val;
+                }
+            }
+            keys[size] = key;
+            values[size] = value;
+            size++;
+            if (size == keys.length) {
+                keys = grow(keys);
+                values = grow(values);
+            }
+            return null;
+        } else {
+            return map.put(key, value);
+        }
+    }
+
+    public Set<Entry<String, Object>> entrySet() {
+        buildIfNeeded();
+        return map.entrySet();
+    }
+
+    public int size() {
+        if (map == null) {
+            return size;
+        } else {
+            return map.size();
+        }
+    }
+
+    public boolean isEmpty() {
+        if (map == null) {
+            return size == 0;
+        } else {
+            return map.isEmpty();
+        }
+    }
+
+    public boolean containsValue(Object value) {
+        buildIfNeeded();
+        return map.containsValue(value);
+    }
+
+    public boolean containsKey(Object key) {
+        buildIfNeeded();
+        return map.containsKey(key);
+    }
+
+    public Object get(Object key) {
+        buildIfNeeded();
+        return map.get(key);
+    }
+
+    private void buildIfNeeded() {
+        if (map == null) {
+            // added to avoid hash collision attack
+            if (Sys.is1_8OrLater() || (Sys.is1_7() && JDK_MAP_ALTHASHING_SYSPROP != null)) {
+                map = new LinkedHashMap<String, Object>(size, 0.01f);
+            } else {
+                map = new TreeMap<String, Object>();
+            }
+
+            for (int index = 0; index < size; index++) {
+                map.put(keys[index], values[index]);
+            }
+            this.keys = null;
+            this.values = null;
+        }
+    }
+
+    public Object remove(Object key) {
+        buildIfNeeded();
+        return map.remove(key);
+    }
+
+    public void putAll(Map m) {
+        buildIfNeeded();
+        map.putAll(m);
+    }
+
+    public void clear() {
+        if (map == null) {
+            size = 0;
+        } else {
+            map.clear();
+        }
+    }
+
+    public Set<String> keySet() {
+        buildIfNeeded();
+        return map.keySet();
+    }
+
+    public Collection<Object> values() {
+        buildIfNeeded();
+        return map.values();
+    }
+
+    public boolean equals(Object o) {
+        buildIfNeeded();
+        return map.equals(o);
+    }
+
+    public int hashCode() {
+        buildIfNeeded();
+        return map.hashCode();
+    }
+
+    public String toString() {
+        buildIfNeeded();
+        return map.toString();
+    }
+
+    protected Object clone() throws CloneNotSupportedException {
+        if (map == null) {
+            return null;
+        } else {
+            if (map instanceof LinkedHashMap) {
+                return ((LinkedHashMap) map).clone();
+            } else {
+                return new LinkedHashMap(this);
+            }
+        }
+    }
+
+    public LazyMap clearAndCopy() {
+        LazyMap map = new LazyMap();
+        for (int index = 0; index < size; index++) {
+            map.put(keys[index], values[index]);
+        }
+        size = 0;
+        return map;
+    }
+
+    public static <V> V[] grow(V[] array) {
+        Object newArray = Array.newInstance(array.getClass().getComponentType(), array.length * 2);
+        System.arraycopy(array, 0, newArray, 0, array.length);
+        return (V[]) newArray;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/25e2a386/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/LazyValueMap.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/LazyValueMap.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/LazyValueMap.java
new file mode 100644
index 0000000..7692388
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/LazyValueMap.java
@@ -0,0 +1,247 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.groovy.json.internal;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import static org.apache.groovy.json.internal.Exceptions.die;
+
+/**
+ * This class is important to the performance of the parser.
+ * It stores Value objects in a map where they are evaluated lazily.
+ * This is great for JSONPath types of application, and Object Serialization but not for maps that are going to be stored in a cache.
+ * <p/>
+ * This is because the Value construct is a type of index overlay that merely tracks where the token is located in the buffer,
+ * and what if any thing we noted about it (like can be converted to a decimal number, etc.).
+ * <p/>
+ * To mitigate memory leaks this class along with CharSequenceValue implement two constructs, namely,
+ * chop,  and lazyChop.
+ * <p/>
+ * A chop is when we convert backing buffer of a Value object into a smaller buffer.
+ * A lazyChop is when we do a chop but only when a get operation is called.
+ * <p/>
+ * The lazyChop is performed on the tree that is touched by the JSONPath expression or its ilk.
+ * <p/>
+ * The chop operation can be done during parsing or lazily by storing the values in this construct.
+ *
+ * @author Rick Hightower (insane chipmonk)
+ */
+public class LazyValueMap extends AbstractMap<String, Object> implements ValueMap<String, Object> {
+
+    /**
+     * holds the map that gets lazily created on first access.
+     */
+    private Map<String, Object> map = null;
+    /**
+     * holds the list of items that we are managing.
+     */
+    private Entry<String, Value>[] items;
+    /**
+     * Holds the current number mapping managed by this map.
+     */
+    private int len = 0;
+    /**
+     * Holds whether or not we ae in lazy chop mode or not.
+     */
+    private final boolean lazyChop;
+
+    /**
+     * Keep track if this map has already been chopped so we don't waste time trying to chop it again.
+     */
+    boolean mapChopped = false;
+
+    public LazyValueMap(boolean lazyChop) {
+        this.items = new Entry[5];
+        this.lazyChop = lazyChop;
+    }
+
+    public LazyValueMap(boolean lazyChop, int initialSize) {
+        this.items = new Entry[initialSize];
+        this.lazyChop = lazyChop;
+    }
+
+    /**
+     * Adds a new MapItemValue to the mapping.
+     *
+     * @param miv miv we are adding.
+     */
+    public final void add(MapItemValue miv) {
+        if (len >= items.length) {
+            items = LazyMap.grow(items);
+        }
+        items[len] = miv;
+        len++;
+    }
+
+    /**
+     * Gets the item by key from the mapping.
+     *
+     * @param key to lookup
+     * @return the item for the given key
+     */
+    public final Object get(Object key) {
+        Object object = null;
+
+        /* if the map is null, then we create it. */
+        if (map == null) {
+            buildMap();
+        }
+        object = map.get(key);
+
+        lazyChopIfNeeded(object);
+        return object;
+    }
+
+    /**
+     * If in lazy chop mode, and the object is a Lazy Value Map or a ValueList
+     * then we force a chop operation for each of its items.
+     */
+    private void lazyChopIfNeeded(Object object) {
+        if (lazyChop) {
+            if (object instanceof LazyValueMap) {
+                LazyValueMap m = (LazyValueMap) object;
+                m.chopMap();
+            } else if (object instanceof ValueList) {
+                ValueList list = (ValueList) object;
+                list.chopList();
+            }
+        }
+    }
+
+    /**
+     * Chop this map.
+     */
+    public final void chopMap() {
+        /* if it has been chopped then you have to return. */
+        if (mapChopped) {
+            return;
+        }
+        mapChopped = true;
+
+        /* If the internal map was not create yet, don't. We can chop the value w/o creating the internal map.*/
+        if (this.map == null) {
+            for (int index = 0; index < len; index++) {
+                MapItemValue entry = (MapItemValue) items[index];
+
+                Value value = entry.getValue();
+                if (value == null) continue;
+                if (value.isContainer()) {
+                    chopContainer(value);
+                } else {
+                    value.chop();
+                }
+            }
+        } else {
+            /* Iterate through the map and do the same thing. Make sure children and children of children are chopped.  */
+            for (Map.Entry<String, Object> entry : map.entrySet()) {
+
+                Object object = entry.getValue();
+                if (object instanceof Value) {
+                    Value value = (Value) object;
+                    if (value.isContainer()) {
+                        chopContainer(value);
+                    } else {
+                        value.chop();
+                    }
+                } else if (object instanceof LazyValueMap) {
+                    LazyValueMap m = (LazyValueMap) object;
+                    m.chopMap();
+                } else if (object instanceof ValueList) {
+                    ValueList list = (ValueList) object;
+                    list.chopList();
+                }
+            }
+        }
+    }
+
+    /* We need to chop up this child container. */
+    private static void chopContainer(Value value) {
+        Object obj = value.toValue();
+        if (obj instanceof LazyValueMap) {
+            LazyValueMap map = (LazyValueMap) obj;
+            map.chopMap();
+        } else if (obj instanceof ValueList) {
+            ValueList list = (ValueList) obj;
+            list.chopList();
+        }
+    }
+
+    public Value put(String key, Object value) {
+        die("Not that kind of map");
+        return null;
+    }
+
+    public Set<Entry<String, Object>> entrySet() {
+        if (map == null) {
+            buildMap();
+        }
+        return map.entrySet();
+    }
+
+    private void buildMap() {
+        // added to avoid hash collision attack
+        if (Sys.is1_8OrLater() || (Sys.is1_7() && LazyMap.JDK_MAP_ALTHASHING_SYSPROP != null)) {
+            map = new HashMap<String, Object>(items.length);
+        } else {
+            map = new TreeMap<String, Object>();
+        }
+
+        for (Entry<String, Value> miv : items) {
+            if (miv == null) {
+                break;
+            }
+            map.put(miv.getKey(), miv.getValue().toValue());
+        }
+
+        len = 0;
+        items = null;
+    }
+
+    public Collection<Object> values() {
+        if (map == null) buildMap();
+        return map.values();
+    }
+
+    public int size() {
+        if (map == null) buildMap();
+        return map.size();
+    }
+
+    public String toString() {
+        if (map == null) buildMap();
+        return map.toString();
+    }
+
+    public int len() {
+        return len;
+    }
+
+    public boolean hydrated() {
+        return map != null;
+    }
+
+    public Entry<String, Value>[] items() {
+        return items;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/25e2a386/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/MapItemValue.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/MapItemValue.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/MapItemValue.java
new file mode 100644
index 0000000..8a10c59
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/MapItemValue.java
@@ -0,0 +1,80 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.groovy.json.internal;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.apache.groovy.json.internal.Exceptions.die;
+
+/**
+ * This holds a mapping from value key to value value to maximize laziness.
+ *
+ * @author Rick Hightower
+ */
+public class MapItemValue implements Map.Entry<String, Value> {
+
+    final Value name;
+    final Value value;
+
+    private String key = null;
+
+    private static final boolean internKeys = Boolean.parseBoolean(System.getProperty("groovy.json.implementation.internKeys", "false"));
+
+    protected static ConcurrentHashMap<String, String> internedKeysCache;
+
+    static {
+        if (internKeys) {
+            internedKeysCache = new ConcurrentHashMap<String, String>();
+        }
+    }
+
+    public MapItemValue(Value name, Value value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getKey() {
+        if (key == null) {
+            if (internKeys) {
+                key = name.toString();
+
+                String keyPrime = internedKeysCache.get(key);
+                if (keyPrime == null) {
+                    key = key.intern();
+                    internedKeysCache.put(key, key);
+                } else {
+                    key = keyPrime;
+                }
+            } else {
+                key = name.toString();
+            }
+        }
+        return key;
+    }
+
+    public Value getValue() {
+        return value;
+    }
+
+    public Value setValue(Value value) {
+        Exceptions.die("not that kind of Entry");
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/25e2a386/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/NumberValue.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/NumberValue.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/NumberValue.java
new file mode 100644
index 0000000..525010e
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/NumberValue.java
@@ -0,0 +1,219 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.groovy.json.internal;
+
+import groovy.json.JsonException;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Date;
+
+import static java.lang.Boolean.parseBoolean;
+import static org.apache.groovy.json.internal.CharScanner.isInteger;
+import static org.apache.groovy.json.internal.CharScanner.parseDouble;
+import static org.apache.groovy.json.internal.CharScanner.parseFloat;
+import static org.apache.groovy.json.internal.CharScanner.parseIntFromTo;
+import static org.apache.groovy.json.internal.CharScanner.parseLongFromTo;
+import static org.apache.groovy.json.internal.Exceptions.die;
+import static org.apache.groovy.json.internal.Exceptions.sputs;
+
+/**
+ * @author Rick Hightower
+ */
+public class NumberValue extends java.lang.Number implements Value {
+
+    private char[] buffer;
+    private boolean chopped;
+    private int startIndex;
+    private int endIndex;
+    private final Type type;
+    private Object value;
+
+    public NumberValue(Type type) {
+        this.type = type;
+    }
+
+    public NumberValue() {
+        this.type = null;
+    }
+
+    public NumberValue(boolean chop, Type type, int startIndex, int endIndex, char[] buffer) {
+        this.type = type;
+
+        try {
+            if (chop) {
+                this.buffer = ArrayUtils.copyRange(buffer, startIndex, endIndex);
+                this.startIndex = 0;
+                this.endIndex = this.buffer.length;
+                chopped = true;
+            } else {
+                this.startIndex = startIndex;
+                this.endIndex = endIndex;
+                this.buffer = buffer;
+            }
+        } catch (Exception ex) {
+            Exceptions.handle(sputs("exception", ex, "start", startIndex, "end", endIndex), ex);
+        }
+
+        // Check for a single minus now, rather than finding out later during lazy parsing.
+        if (this.endIndex - this.startIndex == 1 && this.buffer[this.startIndex] == '-') {
+            die("A single minus is not a valid number");
+        }
+
+    }
+
+    public String toString() {
+        if (startIndex == 0 && endIndex == buffer.length) {
+            return FastStringUtils.noCopyStringFromChars(buffer);
+        } else {
+            return new String(buffer, startIndex, (endIndex - startIndex));
+        }
+    }
+
+    public final Object toValue() {
+        return value != null ? value : (value = doToValue());
+    }
+
+    public <T extends Enum> T toEnum(Class<T> cls) {
+        return toEnum(cls, intValue());
+    }
+
+    public static <T extends Enum> T toEnum(Class<T> cls, int value) {
+        T[] enumConstants = cls.getEnumConstants();
+        for (T e : enumConstants) {
+            if (e.ordinal() == value) {
+                return e;
+            }
+        }
+        die("Can't convert ordinal value " + value + " into enum of type " + cls);
+        return null;
+    }
+
+    public boolean isContainer() {
+        return false;
+    }
+
+    private Object doToValue() {
+        switch (type) {
+            case DOUBLE:
+                return bigDecimalValue();
+            case INTEGER:
+                if (isInteger(buffer, startIndex, endIndex - startIndex)) {
+                    return intValue();
+                } else {
+                    return longValue();
+                }
+        }
+        die();
+        return null;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof Value)) return false;
+
+        NumberValue value1 = (NumberValue) o;
+
+        if (endIndex != value1.endIndex) return false;
+        if (startIndex != value1.startIndex) return false;
+        if (!Arrays.equals(buffer, value1.buffer)) return false;
+        if (type != value1.type) return false;
+        return value != null ? value.equals(value1.value) : value1.value == null;
+
+    }
+
+    public int hashCode() {
+        int result = type != null ? type.hashCode() : 0;
+        result = 31 * result + (buffer != null ? Arrays.hashCode(buffer) : 0);
+        result = 31 * result + startIndex;
+        result = 31 * result + endIndex;
+        result = 31 * result + (value != null ? value.hashCode() : 0);
+        return result;
+    }
+
+    public BigDecimal bigDecimalValue() {
+        try {
+            return new BigDecimal(buffer, startIndex, endIndex - startIndex);
+        } catch (NumberFormatException e) {
+            throw new JsonException("unable to parse " + new String(buffer, startIndex, endIndex - startIndex), e);
+        }
+    }
+
+    public BigInteger bigIntegerValue() {
+        return new BigInteger(toString());
+    }
+
+    public String stringValue() {
+        return toString();
+    }
+
+    public String stringValueEncoded() {
+        return toString();
+    }
+
+    public Date dateValue() {
+        return new Date(Dates.utc(longValue()));
+    }
+
+    public int intValue() {
+        return parseIntFromTo(buffer, startIndex, endIndex);
+    }
+
+    public long longValue() {
+        if (isInteger(buffer, startIndex, endIndex - startIndex)) {
+            return parseIntFromTo(buffer, startIndex, endIndex);
+        } else {
+            return parseLongFromTo(buffer, startIndex, endIndex);
+        }
+    }
+
+    public byte byteValue() {
+        return (byte) intValue();
+    }
+
+    public short shortValue() {
+        return (short) intValue();
+    }
+
+    public double doubleValue() {
+        return parseDouble(this.buffer, startIndex, endIndex);
+    }
+
+    public boolean booleanValue() {
+        return parseBoolean(toString());
+    }
+
+    public float floatValue() {
+        return parseFloat(this.buffer, startIndex, endIndex);
+    }
+
+    public final void chop() {
+        if (!chopped) {
+            this.chopped = true;
+            this.buffer = ArrayUtils.copyRange(buffer, startIndex, endIndex);
+            this.startIndex = 0;
+            this.endIndex = this.buffer.length;
+        }
+    }
+
+    public char charValue() {
+        return buffer[startIndex];
+    }
+}


Mime
View raw message