You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2018/02/28 11:37:53 UTC

[4/7] groovy git commit: GROOVY-8379: Rework groovy-json FastStringUtils (closes #667)

http://git-wip-us.apache.org/repos/asf/groovy/blob/5a3f9996/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharBuf.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharBuf.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharBuf.java
new file mode 100644
index 0000000..db850e1
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharBuf.java
@@ -0,0 +1,842 @@
+/*
+ *  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.IOException;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * @author Rick Hightower
+ */
+public class CharBuf extends Writer implements CharSequence {
+
+    protected int capacity = 16;
+    protected int location = 0;
+
+    protected char[] buffer;
+
+    public CharBuf(char[] buffer) {
+        __init__(buffer);
+    }
+
+    private void __init__(char[] buffer) {
+        this.buffer = buffer;
+        this.capacity = buffer.length;
+    }
+
+    public CharBuf(byte[] bytes) {
+        this.buffer = null;
+        try {
+            String str = new String(bytes, "UTF-8");
+            __init__(FastStringUtils.toCharArray(str));
+        } catch (UnsupportedEncodingException e) {
+            Exceptions.handle(e);
+        }
+    }
+
+    public static CharBuf createExact(final int capacity) {
+        return new CharBuf(capacity) {
+            public CharBuf add(char[] chars) {
+                Chr._idx(buffer, location, chars);
+                location += chars.length;
+                return this;
+            }
+        };
+    }
+
+    public static CharBuf create(int capacity) {
+        return new CharBuf(capacity);
+    }
+
+    public static CharBuf create(char[] buffer) {
+        return new CharBuf(buffer);
+    }
+
+    protected CharBuf(int capacity) {
+        this.capacity = capacity;
+        init();
+    }
+
+    protected CharBuf() {
+        init();
+    }
+
+    public void write(char[] cbuf, int off, int len) {
+        if (off == 0 && cbuf.length == len) {
+            this.add(cbuf);
+        } else {
+            char[] buffer = ArrayUtils.copyRange(cbuf, off, off + len);
+            this.add(buffer);
+        }
+    }
+
+    public void flush() throws IOException {
+    }
+
+    public void close() throws IOException {
+    }
+
+    public void init() {
+        buffer = new char[capacity];
+    }
+
+    public final CharBuf add(String str) {
+        add(FastStringUtils.toCharArray(str));
+        return this;
+    }
+
+    public final CharBuf addString(String str) {
+        add(FastStringUtils.toCharArray(str));
+        return this;
+    }
+
+    public final CharBuf add(int i) {
+        add(Integer.toString(i));
+        return this;
+    }
+
+    private Cache<Integer, char[]> icache;
+
+    public final CharBuf addInt(int i) {
+        switch (i) {
+            case 0:
+                addChar('0');
+                return this;
+            case 1:
+                addChar('1');
+                return this;
+            case -1:
+                addChar('-');
+                addChar('1');
+                return this;
+        }
+
+        addInt(Integer.valueOf(i));
+        return this;
+    }
+
+    public final CharBuf addInt(Integer key) {
+        if (icache == null) {
+            icache = new SimpleCache<Integer, char[]>(20);
+        }
+        char[] chars = icache.get(key);
+
+        if (chars == null) {
+            String str = Integer.toString(key);
+            chars = FastStringUtils.toCharArray(str);
+            icache.put(key, chars);
+        }
+
+        addChars(chars);
+        return this;
+    }
+
+    final char[] trueChars = "true".toCharArray();
+    final char[] falseChars = "false".toCharArray();
+
+    public final CharBuf add(boolean b) {
+        addChars(b ? trueChars : falseChars);
+        return this;
+    }
+
+    public final CharBuf addBoolean(boolean b) {
+        add(Boolean.toString(b));
+        return this;
+    }
+
+    public final CharBuf add(byte i) {
+        add(Byte.toString(i));
+        return this;
+    }
+
+    public final CharBuf addByte(byte i) {
+        addInt(i);
+        return this;
+    }
+
+    public final CharBuf add(short i) {
+        add(Short.toString(i));
+        return this;
+    }
+
+    public final CharBuf addShort(short i) {
+        addInt(i);
+        return this;
+    }
+
+    public final CharBuf add(long l) {
+        add(Long.toString(l));
+        return this;
+    }
+
+    public final CharBuf add(double d) {
+        add(Double.toString(d));
+        return this;
+    }
+
+    private Cache<Double, char[]> dcache;
+
+    public final CharBuf addDouble(double d) {
+        addDouble(Double.valueOf(d));
+        return this;
+    }
+
+    public final CharBuf addDouble(Double key) {
+        if (dcache == null) {
+            dcache = new SimpleCache<Double, char[]>(20);
+        }
+        char[] chars = dcache.get(key);
+
+        if (chars == null) {
+            String str = Double.toString(key);
+            chars = FastStringUtils.toCharArray(str);
+            dcache.put(key, chars);
+        }
+
+        add(chars);
+        return this;
+    }
+
+    public final CharBuf add(float d) {
+        add(Float.toString(d));
+        return this;
+    }
+
+    private Cache<Float, char[]> fcache;
+
+    public final CharBuf addFloat(float d) {
+        addFloat(Float.valueOf(d));
+        return this;
+    }
+
+    public final CharBuf addFloat(Float key) {
+        if (fcache == null) {
+            fcache = new SimpleCache<Float, char[]>(20);
+        }
+        char[] chars = fcache.get(key);
+
+        if (chars == null) {
+            String str = Float.toString(key);
+            chars = FastStringUtils.toCharArray(str);
+            fcache.put(key, chars);
+        }
+
+        add(chars);
+
+        return this;
+    }
+
+    public final CharBuf addChar(byte i) {
+        add((char) i);
+        return this;
+    }
+
+    public final CharBuf addChar(int i) {
+        add((char) i);
+        return this;
+    }
+
+    public final CharBuf addChar(short i) {
+        add((char) i);
+        return this;
+    }
+
+    public final CharBuf addChar(final char ch) {
+        int _location = location;
+        char[] _buffer = buffer;
+        int _capacity = capacity;
+
+        if (1 + _location > _capacity) {
+            _buffer = Chr.grow(_buffer);
+            _capacity = _buffer.length;
+        }
+
+        _buffer[_location] = ch;
+        _location++;
+
+        location = _location;
+        buffer = _buffer;
+        capacity = _capacity;
+        return this;
+    }
+
+    public CharBuf addLine(String str) {
+        add(str.toCharArray());
+        add('\n');
+        return this;
+    }
+
+    public CharBuf addLine(CharSequence str) {
+        add(str.toString());
+        add('\n');
+        return this;
+    }
+
+    public CharBuf add(char[] chars) {
+        if (chars.length + location > capacity) {
+            buffer = Chr.grow(buffer, buffer.length * 2 + chars.length);
+            capacity = buffer.length;
+        }
+
+        Chr._idx(buffer, location, chars);
+        location += chars.length;
+        return this;
+    }
+
+    public final CharBuf addChars(char[] chars) {
+        if (chars.length + location > capacity) {
+            buffer = Chr.grow(buffer, buffer.length * 2 + chars.length);
+            capacity = buffer.length;
+        }
+
+        System.arraycopy(chars, 0, buffer, location, chars.length);
+        location += chars.length;
+        return this;
+    }
+
+    public final CharBuf addQuoted(char[] chars) {
+        int _location = location;
+        char[] _buffer = buffer;
+        int _capacity = capacity;
+
+        int sizeNeeded = chars.length + 2 + _location;
+        if (sizeNeeded > _capacity) {
+            _buffer = Chr.grow(_buffer, sizeNeeded * 2);
+            _capacity = _buffer.length;
+        }
+        _buffer[_location] = '"';
+        _location++;
+
+        System.arraycopy(chars, 0, _buffer, _location, chars.length);
+
+        _location += (chars.length);
+        _buffer[_location] = '"';
+        _location++;
+
+        location = _location;
+        buffer = _buffer;
+        capacity = _capacity;
+        return this;
+    }
+
+    public final CharBuf addJsonEscapedString(String jsonString) {
+        return addJsonEscapedString(jsonString, false);
+    }
+
+    public final CharBuf addJsonEscapedString(String jsonString, boolean disableUnicodeEscaping) {
+        char[] charArray = FastStringUtils.toCharArray(jsonString);
+        return addJsonEscapedString(charArray, disableUnicodeEscaping);
+    }
+
+    private static boolean shouldEscape(int c, boolean disableUnicodeEscaping) {
+        if (c < 32) { /* less than space is a control char */
+            return true;
+        } else if (c == 34) {  /* double quote */
+            return true;
+        } else if (c == 92) {  /* backslash */
+            return true;
+        } else if (!disableUnicodeEscaping && c > 126) {  /* non-ascii char range */
+            return true;
+        }
+
+        return false;
+    }
+
+    private static boolean hasAnyJSONControlChars(final char[] charArray, boolean disableUnicodeEscaping) {
+        int index = 0;
+        char c;
+        while (true) {
+            c = charArray[index];
+            if (shouldEscape(c, disableUnicodeEscaping)) {
+                return true;
+            }
+            if (++index >= charArray.length) return false;
+        }
+    }
+
+    public final CharBuf addJsonEscapedString(final char[] charArray) {
+        return addJsonEscapedString(charArray, false);
+    }
+
+    public final CharBuf addJsonEscapedString(final char[] charArray, boolean disableUnicodeEscaping) {
+        if (charArray.length == 0) return this;
+        if (hasAnyJSONControlChars(charArray, disableUnicodeEscaping)) {
+            return doAddJsonEscapedString(charArray, disableUnicodeEscaping);
+        } else {
+            return this.addQuoted(charArray);
+        }
+    }
+
+    final byte[] encoded = new byte[2];
+
+    final byte[] charTo = new byte[2];
+
+    private CharBuf doAddJsonEscapedString(char[] charArray, boolean disableUnicodeEscaping) {
+        char[] _buffer = buffer;
+        int _location = this.location;
+
+        final byte[] _encoded = encoded;
+
+        final byte[] _charTo = charTo;
+        /* We are making a bet that not all chars will be unicode. */
+        int ensureThisMuch = charArray.length * 6 + 2;
+
+        int sizeNeeded = (ensureThisMuch) + _location;
+        if (sizeNeeded > capacity) {
+            int growBy = (_buffer.length * 2) < sizeNeeded ? sizeNeeded : (_buffer.length * 2);
+            _buffer = Chr.grow(buffer, growBy);
+            capacity = _buffer.length;
+        }
+
+        _buffer[_location] = '"';
+        _location++;
+
+        int index = 0;
+        while (true) {
+            char c = charArray[index];
+
+            if (shouldEscape(c, disableUnicodeEscaping)) {
+                   /* We are covering our bet with a safety net.
+                      otherwise we would have to have 5x buffer
+                      allocated for control chars */
+                if (_location + 5 > _buffer.length) {
+                    _buffer = Chr.grow(_buffer, 20);
+                }
+
+                switch (c) {
+                    case '\"':
+                        _buffer[_location] = '\\';
+                        _location++;
+                        _buffer[_location] = '"';
+                        _location++;
+                        break;
+                    case '\\':
+                        _buffer[_location] = '\\';
+                        _location++;
+                        _buffer[_location] = '\\';
+                        _location++;
+                        break;
+                    //There is not requirement to escape solidus so we will not.
+//                        case '/':
+//                            _buffer[_location] = '\\';
+//                            _location ++;
+//                            _buffer[_location] =  '/';
+//                            _location ++;
+//                            break;
+
+                    case '\b':
+                        _buffer[_location] = '\\';
+                        _location++;
+                        _buffer[_location] = 'b';
+                        _location++;
+                        break;
+                    case '\f':
+                        _buffer[_location] = '\\';
+                        _location++;
+                        _buffer[_location] = 'f';
+                        _location++;
+                        break;
+                    case '\n':
+                        _buffer[_location] = '\\';
+                        _location++;
+                        _buffer[_location] = 'n';
+                        _location++;
+                        break;
+                    case '\r':
+                        _buffer[_location] = '\\';
+                        _location++;
+                        _buffer[_location] = 'r';
+                        _location++;
+                        break;
+                    case '\t':
+                        _buffer[_location] = '\\';
+                        _location++;
+                        _buffer[_location] = 't';
+                        _location++;
+                        break;
+                    default:
+                        _buffer[_location] = '\\';
+                        _location++;
+                        _buffer[_location] = 'u';
+                        _location++;
+                        if (c <= 255) {
+                            _buffer[_location] = '0';
+                            _location++;
+                            _buffer[_location] = '0';
+                            _location++;
+                            ByteScanner.encodeByteIntoTwoAsciiCharBytes(c, _encoded);
+                            for (int b : _encoded) {
+                                _buffer[_location] = (char) b;
+                                _location++;
+                            }
+                        } else {
+                            _charTo[1] = (byte) (c);
+                            _charTo[0] = (byte) (c >>> 8);
+
+                            for (int charByte : _charTo) {
+                                ByteScanner.encodeByteIntoTwoAsciiCharBytes(charByte, _encoded);
+                                for (int b : _encoded) {
+                                    _buffer[_location] = (char) b;
+                                    _location++;
+                                }
+                            }
+                        }
+                }
+            } else {
+                _buffer[_location] = c;
+                _location++;
+            }
+
+            if (++index >= charArray.length) break;
+        }
+        _buffer[_location] = '"';
+        _location++;
+
+        buffer = _buffer;
+        location = _location;
+
+        return this;
+    }
+
+    public final CharBuf addJsonFieldName(String str) {
+        return addJsonFieldName(str, false);
+    }
+
+    public final CharBuf addJsonFieldName(String str, boolean disableUnicodeEscaping) {
+        return addJsonFieldName(FastStringUtils.toCharArray(str), disableUnicodeEscaping);
+    }
+
+    private static final char[] EMPTY_STRING_CHARS = Chr.array('"', '"');
+
+    public final CharBuf addJsonFieldName(char[] chars) {
+        return addJsonFieldName(chars, false);
+    }
+
+    public final CharBuf addJsonFieldName(char[] chars, boolean disableUnicodeEscaping) {
+        if (chars.length > 0) {
+            addJsonEscapedString(chars, disableUnicodeEscaping);
+        } else {
+            addChars(EMPTY_STRING_CHARS);
+        }
+        addChar(':');
+        return this;
+    }
+
+    public final CharBuf addQuoted(String str) {
+        final char[] chars = FastStringUtils.toCharArray(str);
+        addQuoted(chars);
+        return this;
+    }
+
+    public CharBuf add(char[] chars, final int length) {
+        if (length + location < capacity) {
+            Chr._idx(buffer, location, chars, length);
+        } else {
+            buffer = Chr.grow(buffer, buffer.length * 2 + length);
+            Chr._idx(buffer, location, chars);
+            capacity = buffer.length;
+        }
+        location += length;
+        return this;
+    }
+
+    public CharBuf add(byte[] chars) {
+        if (chars.length + location < capacity) {
+            Chr._idx(buffer, location, chars);
+        } else {
+            buffer = Chr.grow(buffer, buffer.length * 2 + chars.length);
+            Chr._idx(buffer, location, chars);
+            capacity = buffer.length;
+        }
+        location += chars.length;
+        return this;
+    }
+
+    public CharBuf add(byte[] bytes, int start, int end) {
+        int charsLength = end - start;
+        if (charsLength + location > capacity) {
+            buffer = Chr.grow(buffer, buffer.length * 2 + charsLength);
+        }
+        Chr._idx(buffer, location, bytes, start, end);
+        capacity = buffer.length;
+        location += charsLength;
+        return this;
+    }
+
+    public final CharBuf add(char ch) {
+        if (1 + location < capacity) {
+            buffer[location] = ch;
+        } else {
+            buffer = Chr.grow(buffer);
+            buffer[location] = ch;
+            capacity = buffer.length;
+        }
+        location += 1;
+        return this;
+    }
+
+    public int length() {
+        return len();
+    }
+
+    public char charAt(int index) {
+        return buffer[index];
+    }
+
+    public CharSequence subSequence(int start, int end) {
+        return new String(buffer, start, end - start);
+    }
+
+    public String toString() {
+        return new String(buffer, 0, location);
+    }
+
+    public String toDebugString() {
+        return "CharBuf{" +
+                "capacity=" + capacity +
+                ", location=" + location +
+                '}';
+    }
+
+    public String toStringAndRecycle() {
+        String str = new String(buffer, 0, location);
+        location = 0;
+        return str;
+    }
+
+    public int len() {
+        return location;
+    }
+
+    public char[] toCharArray() {
+        return this.buffer;
+    }
+
+    public void _len(int location) {
+        this.location = location;
+    }
+
+    public char[] readForRecycle() {
+        this.location = 0;
+        return this.buffer;
+    }
+
+    public void recycle() {
+        this.location = 0;
+    }
+
+    public double doubleValue() {
+        return CharScanner.parseDouble(this.buffer, 0, location);
+    }
+
+    public float floatValue() {
+        return CharScanner.parseFloat(this.buffer, 0, location);
+    }
+
+    public int intValue() {
+        return CharScanner.parseIntFromTo(buffer, 0, location);
+    }
+
+    public long longValue() {
+        return CharScanner.parseLongFromTo(buffer, 0, location);
+    }
+
+    public byte byteValue() {
+        return (byte) intValue();
+    }
+
+    public short shortValue() {
+        return (short) intValue();
+    }
+
+    public Number toIntegerWrapper() {
+        if (CharScanner.isInteger(buffer, 0, location)) {
+            return intValue();
+        } else {
+            return longValue();
+        }
+    }
+
+    static final char[] nullChars = "null".toCharArray();
+
+    public final void addNull() {
+        this.add(nullChars);
+    }
+
+    public void removeLastChar() {
+        if (location > 0) {
+            location--;
+        }
+    }
+
+    public void removeLastChar(char expect) {
+        if (location == 0 || buffer[location-1] != expect) {
+            return;
+        }
+        removeLastChar();
+    }
+
+    private Cache<BigDecimal, char[]> bigDCache;
+
+    public CharBuf addBigDecimal(BigDecimal key) {
+        if (bigDCache == null) {
+            bigDCache = new SimpleCache<BigDecimal, char[]>(20);
+        }
+        char[] chars = bigDCache.get(key);
+
+        if (chars == null) {
+            String str = key.toString();
+            chars = FastStringUtils.toCharArray(str);
+            bigDCache.put(key, chars);
+        }
+
+        add(chars);
+
+        return this;
+    }
+
+    private Cache<BigInteger, char[]> bigICache;
+
+    public CharBuf addBigInteger(BigInteger key) {
+        if (bigICache == null) {
+            bigICache = new SimpleCache<BigInteger, char[]>(20);
+        }
+        char[] chars = bigICache.get(key);
+
+        if (chars == null) {
+            String str = key.toString();
+            chars = FastStringUtils.toCharArray(str);
+            bigICache.put(key, chars);
+        }
+
+        add(chars);
+
+        return this;
+    }
+
+    private Cache<Long, char[]> lcache;
+
+    public final CharBuf addLong(long l) {
+        addLong(Long.valueOf(l));
+        return this;
+    }
+
+    public final CharBuf addLong(Long key) {
+        if (lcache == null) {
+            lcache = new SimpleCache<Long, char[]>(20);
+        }
+        char[] chars = lcache.get(key);
+
+        if (chars == null) {
+            String str = Long.toString(key);
+            chars = FastStringUtils.toCharArray(str);
+            lcache.put(key, chars);
+        }
+
+        add(chars);
+
+        return this;
+    }
+
+    public final CharBuf decodeJsonString(char[] chars) {
+        return decodeJsonString(chars, 0, chars.length);
+    }
+
+    public final CharBuf decodeJsonString(char[] chars, int start, int to) {
+        int len = to - start;
+
+        char[] buffer = this.buffer;
+        int location = this.location;
+
+        if (len > capacity) {
+            buffer = Chr.grow(buffer, buffer.length * 2 + len);
+            capacity = buffer.length;
+        }
+
+        for (int index = start; index < to; index++) {
+            char c = chars[index];
+            if (c == '\\') {
+                if (index < to) {
+                    index++;
+                    c = chars[index];
+                    switch (c) {
+
+                        case 'n':
+                            buffer[location++] = '\n';
+                            break;
+
+                        case '/':
+                            buffer[location++] = '/';
+                            break;
+
+                        case '"':
+                            buffer[location++] = '"';
+                            break;
+
+                        case 'f':
+                            buffer[location++] = '\f';
+                            break;
+
+                        case 't':
+                            buffer[location++] = '\t';
+                            break;
+
+                        case '\\':
+                            buffer[location++] = '\\';
+                            break;
+
+                        case 'b':
+                            buffer[location++] = '\b';
+                            break;
+
+                        case 'r':
+                            buffer[location++] = '\r';
+                            break;
+
+                        case 'u':
+                            if (index + 4 < to) {
+                                String hex = new String(chars, index + 1, 4);
+                                char unicode = (char) Integer.parseInt(hex, 16);
+                                buffer[location++] = unicode;
+                                index += 4;
+                            }
+                            break;
+
+                        default:
+                            throw new JsonException("Unable to decode string");
+                    }
+                }
+            } else {
+                buffer[location++] = c;
+            }
+        }
+
+        this.buffer = buffer;
+        this.location = location;
+
+        return this;
+    }
+}
+
+

http://git-wip-us.apache.org/repos/asf/groovy/blob/5a3f9996/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharScanner.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharScanner.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharScanner.java
new file mode 100644
index 0000000..2dfacf9
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharScanner.java
@@ -0,0 +1,512 @@
+/*
+ *  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.math.BigDecimal;
+
+import static org.apache.groovy.json.internal.Exceptions.die;
+import static org.apache.groovy.json.internal.Exceptions.handle;
+
+/**
+ * @author Richard Hightower
+ */
+public class CharScanner {
+
+    protected static final int COMMA = ',';
+    protected static final int CLOSED_CURLY = '}';
+    protected static final int CLOSED_BRACKET = ']';
+    protected static final int LETTER_E = 'e';
+    protected static final int LETTER_BIG_E = 'E';
+    protected static final int DECIMAL_POINT = '.';
+    protected static final int ALPHA_0 = '0';
+    protected static final int ALPHA_9 = '9';
+    protected static final int MINUS = '-';
+    protected static final int PLUS = '+';
+
+    static final String MIN_LONG_STR_NO_SIGN = String.valueOf(Long.MIN_VALUE);
+    static final String MAX_LONG_STR = String.valueOf(Long.MAX_VALUE);
+    static final String MIN_INT_STR_NO_SIGN = String.valueOf(Integer.MIN_VALUE);
+    static final String MAX_INT_STR = String.valueOf(Integer.MAX_VALUE);
+
+    private static double powersOf10[] = {
+            1.0,
+            10.0,
+            100.0,
+            1000.0,
+            10000.0,
+            100000.0,
+            1000000.0,
+            10000000.0,
+            100000000.0,
+            1000000000.0,
+            10000000000.0,
+            100000000000.0,
+            1000000000000.0,
+            10000000000000.0,
+            100000000000000.0,
+            1000000000000000.0,
+            10000000000000000.0,
+            100000000000000000.0,
+            1000000000000000000.0,
+    };
+
+    public static boolean isDigit(int c) {
+        return c >= ALPHA_0 && c <= ALPHA_9;
+    }
+
+    public static boolean isDecimalDigit(int c) {
+        return isDigit(c) || isDecimalChar(c);
+    }
+
+    public static boolean isDecimalChar(int currentChar) {
+        switch (currentChar) {
+            case MINUS:
+            case PLUS:
+            case LETTER_E:
+            case LETTER_BIG_E:
+            case DECIMAL_POINT:
+                return true;
+        }
+        return false;
+    }
+
+    public static boolean hasDecimalChar(char[] chars, boolean negative) {
+        int index = 0;
+
+        if (negative) index++;
+
+        for (; index < chars.length; index++) {
+            switch (chars[index]) {
+                case MINUS:
+                case PLUS:
+                case LETTER_E:
+                case LETTER_BIG_E:
+                case DECIMAL_POINT:
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isLong(char[] digitChars) {
+        return isLong(digitChars, 0, digitChars.length);
+    }
+
+    public static boolean isLong(char[] digitChars, int offset, int len) {
+        String cmpStr = digitChars[offset] == '-' ? MIN_LONG_STR_NO_SIGN : MAX_LONG_STR;
+        int cmpLen = cmpStr.length();
+        if (len < cmpLen) return true;
+        if (len > cmpLen) return false;
+
+        for (int i = 0; i < cmpLen; ++i) {
+            int diff = digitChars[offset + i] - cmpStr.charAt(i);
+            if (diff != 0) {
+                return (diff < 0);
+            }
+        }
+        return true;
+    }
+
+    public static boolean isInteger(char[] digitChars) {
+        return isInteger(digitChars, 0, digitChars.length);
+    }
+
+    public static boolean isInteger(char[] digitChars, int offset, int len) {
+        String cmpStr = (digitChars[offset] == '-') ? MIN_INT_STR_NO_SIGN : MAX_INT_STR;
+        int cmpLen = cmpStr.length();
+        if (len < cmpLen) return true;
+        if (len > cmpLen) return false;
+
+        for (int i = 0; i < cmpLen; ++i) {
+            int diff = digitChars[offset + i] - cmpStr.charAt(i);
+            if (diff != 0) {
+                return (diff < 0);
+            }
+        }
+        return true;
+    }
+
+    public static int parseInt(char[] digitChars) {
+        return parseIntFromTo(digitChars, 0, digitChars.length);
+    }
+
+    public static int parseIntFromTo(char[] digitChars, int offset, int to) {
+        try {
+            int num;
+            boolean negative = false;
+            char c = digitChars[offset];
+            if (c == '-') {
+                offset++;
+                negative = true;
+            }
+            if (offset >= to) {
+                die();
+            }
+            num = (digitChars[offset] - '0');
+            if (++offset < to) {
+                num = (num * 10) + (digitChars[offset] - '0');
+                if (++offset < to) {
+                    num = (num * 10) + (digitChars[offset] - '0');
+                    if (++offset < to) {
+                        num = (num * 10) + (digitChars[offset] - '0');
+                        if (++offset < to) {
+                            num = (num * 10) + (digitChars[offset] - '0');
+                            if (++offset < to) {
+                                num = (num * 10) + (digitChars[offset] - '0');
+                                if (++offset < to) {
+                                    num = (num * 10) + (digitChars[offset] - '0');
+                                    if (++offset < to) {
+                                        num = (num * 10) + (digitChars[offset] - '0');
+                                        if (++offset < to) {
+                                            num = (num * 10) + (digitChars[offset] - '0');
+                                            if (++offset < to) {
+                                                num = (num * 10) + (digitChars[offset] - '0');
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            return negative ? num * -1 : num;
+        } catch (Exception ex) {
+            return handle(int.class, ex);
+        }
+    }
+
+    public static int parseIntFromToIgnoreDot(char[] digitChars, int offset, int to) {
+        int num;
+        boolean negative = false;
+        char c = digitChars[offset];
+        if (c == '-') {
+            offset++;
+            negative = true;
+        }
+        if (offset >= to) {
+            die();
+        }
+        c = digitChars[offset];
+        num = (c - '0');
+        offset++;
+
+        for (; offset < to; offset++) {
+            c = digitChars[offset];
+            if (c != '.') {
+                num = (num * 10) + (c - '0');
+            }
+        }
+
+        return negative ? num * -1 : num;
+    }
+
+    public static long parseLongFromToIgnoreDot(char[] digitChars, int offset, int to) {
+        long num;
+        boolean negative = false;
+        char c = digitChars[offset];
+        if (c == '-') {
+            offset++;
+            negative = true;
+        }
+        if (offset >= to) {
+            die();
+        }
+        c = digitChars[offset];
+        num = (c - '0');
+        offset++;
+
+        for (; offset < to; offset++) {
+            c = digitChars[offset];
+            if (c != '.') {
+                num = (num * 10) + (c - '0');
+            }
+        }
+
+        return negative ? num * -1 : num;
+    }
+
+    public static long parseLongFromTo(char[] digitChars, int offset, int to) {
+        long num;
+        boolean negative = false;
+        char c = digitChars[offset];
+        if (c == '-') {
+            offset++;
+            negative = true;
+        }
+        if (offset >= to) {
+            die();
+        }
+        c = digitChars[offset];
+        num = (c - '0');
+        offset++;
+
+        long digit;
+
+        for (; offset < to; offset++) {
+            c = digitChars[offset];
+            digit = (c - '0');
+            num = (num * 10) + digit;
+        }
+
+        return negative ? num * -1 : num;
+    }
+
+    public static long parseLong(char[] digitChars) {
+        return parseLongFromTo(digitChars, 0, digitChars.length);
+    }
+
+    public static Number parseJsonNumber(char[] buffer) {
+        return parseJsonNumber(buffer, 0, buffer.length);
+    }
+
+    public static Number parseJsonNumber(char[] buffer, int from, int to) {
+        return parseJsonNumber(buffer, from, to, null);
+    }
+
+    public static boolean isNumberDigit(int c) {
+        return c >= ALPHA_0 && c <= ALPHA_9;
+    }
+
+    protected static boolean isDelimiter(int c) {
+        return c == COMMA || c == CLOSED_CURLY || c == CLOSED_BRACKET;
+    }
+
+    public static Number parseJsonNumber(char[] buffer, int from, int max, int size[]) {
+        Number value = null;
+        boolean simple = true;
+        int digitsPastPoint = 0;
+
+        int index = from;
+
+        if (buffer[index] == '-') {
+            index++;
+        }
+        if (index >= max) {
+            die();
+        }
+
+        boolean foundDot = false;
+        for (; index < max; index++) {
+            char ch = buffer[index];
+            if (isNumberDigit(ch)) {
+                if (foundDot) {
+                    digitsPastPoint++;
+                }
+            } else if (ch <= 32 || isDelimiter(ch)) {
+                break;
+            } else if (ch == '.') {
+                if (foundDot) {
+                    die("unexpected character " + ch);
+                }
+                foundDot = true;
+            } else if (ch == 'E' || ch == 'e' || ch == '-' || ch == '+') {
+                simple = false;
+            } else {
+                die("unexpected character " + ch);
+            }
+        }
+
+        if (digitsPastPoint >= powersOf10.length - 1) {
+            simple = false;
+        }
+
+        final int length = index - from;
+
+        if (!foundDot && simple) {
+            if (isInteger(buffer, from, length)) {
+                value = parseIntFromTo(buffer, from, index);
+            } else {
+                value = parseLongFromTo(buffer, from, index);
+            }
+        } else {
+            value = new BigDecimal(buffer, from, length);
+        }
+
+        if (size != null) {
+            size[0] = index;
+        }
+
+        return value;
+    }
+
+    public static BigDecimal parseBigDecimal(char[] buffer) {
+        return new BigDecimal(buffer);
+    }
+
+    public static float parseFloat(char[] buffer, int from, int to) {
+        return (float) parseDouble(buffer, from, to);
+    }
+
+    public static double parseDouble(char[] buffer, int from, int to) {
+        double value;
+        boolean simple = true;
+        int digitsPastPoint = 0;
+
+        int index = from;
+
+        if (buffer[index] == '-') {
+            index++;
+        }
+
+        boolean foundDot = false;
+        for (; index < to; index++) {
+            char ch = buffer[index];
+            if (isNumberDigit(ch)) {
+                if (foundDot) {
+                    digitsPastPoint++;
+                }
+            } else if (ch == '.') {
+                if (foundDot) {
+                    die("unexpected character " + ch);
+                }
+                foundDot = true;
+            } else if (ch == 'E' || ch == 'e' || ch == '-' || ch == '+') {
+                simple = false;
+            } else {
+                die("unexpected character " + ch);
+            }
+        }
+
+        if (digitsPastPoint >= powersOf10.length - 1) {
+            simple = false;
+        }
+
+        final int length = index - from;
+
+        if (!foundDot && simple) {
+            if (isInteger(buffer, from, length)) {
+                value = parseIntFromTo(buffer, from, index);
+            } else {
+                value = parseLongFromTo(buffer, from, index);
+            }
+        } else if (foundDot && simple) {
+            long lvalue;
+
+            if (length < powersOf10.length) {
+                if (isInteger(buffer, from, length)) {
+                    lvalue = parseIntFromToIgnoreDot(buffer, from, index);
+                } else {
+                    lvalue = parseLongFromToIgnoreDot(buffer, from, index);
+                }
+
+                double power = powersOf10[digitsPastPoint];
+                value = lvalue / power;
+            } else {
+                value = Double.parseDouble(new String(buffer, from, length));
+            }
+        } else {
+            value = Double.parseDouble(new String(buffer, from, index - from));
+        }
+
+        return value;
+    }
+
+    public static int skipWhiteSpace(char[] array, int index, final int length) {
+        int c;
+        for (; index < length; index++) {
+            c = array[index];
+            if (c > 32) {
+                return index;
+            }
+        }
+        return index;
+    }
+
+    public static char[] readNumber(char[] array, int idx, final int len) {
+        final int startIndex = idx;
+
+        while (true) {
+            if (!CharScanner.isDecimalDigit(array[idx])) {
+                break;
+            } else {
+                idx++;
+                if (idx >= len) break;
+            }
+        }
+
+        return ArrayUtils.copyRange(array, startIndex, idx);
+    }
+
+    public static String errorDetails(String message, char[] array, int index, int ch) {
+        CharBuf buf = CharBuf.create(255);
+
+        buf.addLine(message);
+
+        buf.addLine("");
+        buf.addLine("The current character read is " + debugCharDescription(ch));
+
+        buf.addLine(message);
+
+        int line = 0;
+        int lastLineIndex = 0;
+
+        for (int i = 0; i < index && i < array.length; i++) {
+            if (array[i] == '\n') {
+                line++;
+                lastLineIndex = i + 1;
+            }
+        }
+
+        int count = 0;
+
+        for (int i = lastLineIndex; i < array.length; i++, count++) {
+            if (array[i] == '\n') {
+                break;
+            }
+        }
+
+        buf.addLine("line number " + (line + 1));
+        buf.addLine("index number " + index);
+
+        try {
+            buf.addLine(new String(array, lastLineIndex, count));
+        } catch (Exception ex) {
+            try {
+                int start = index = (index - 10 < 0) ? 0 : index - 10;
+
+                buf.addLine(new String(array, start, index));
+            } catch (Exception ex2) {
+                buf.addLine(new String(array, 0, array.length));
+            }
+        }
+        for (int i = 0; i < (index - lastLineIndex); i++) {
+            buf.add('.');
+        }
+        buf.add('^');
+
+        return buf.toString();
+    }
+
+    public static String debugCharDescription(int c) {
+        String charString;
+        if (c == ' ') {
+            charString = "[SPACE]";
+        } else if (c == '\t') {
+            charString = "[TAB]";
+        } else if (c == '\n') {
+            charString = "[NEWLINE]";
+        } else {
+            charString = "'" + (char) c + "'";
+        }
+
+        charString = charString + " with an int value of " + ((int) c);
+        return charString;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/5a3f9996/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharSequenceValue.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharSequenceValue.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharSequenceValue.java
new file mode 100644
index 0000000..40ebc16
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharSequenceValue.java
@@ -0,0 +1,278 @@
+/*
+ *  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 org.apache.groovy.json.internal.CharScanner.isInteger;
+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;
+
+/**
+ * @author Rick Hightower
+ */
+public class CharSequenceValue implements Value, CharSequence {
+
+    private final Type type;
+    private final boolean checkDate;
+    private final boolean decodeStrings;
+
+    private char[] buffer;
+    private boolean chopped;
+    private int startIndex;
+    private int endIndex;
+    private Object value;
+
+    public CharSequenceValue(boolean chop, Type type, int startIndex, int endIndex, char[] buffer,
+                             boolean encoded, boolean checkDate) {
+        this.type = type;
+        this.checkDate = checkDate;
+        this.decodeStrings = encoded;
+
+        if (chop) {
+            try {
+                this.buffer = ArrayUtils.copyRange(buffer, startIndex, endIndex);
+            } catch (Exception ex) {
+                Exceptions.handle(ex);
+            }
+            this.startIndex = 0;
+            this.endIndex = this.buffer.length;
+            this.chopped = true;
+        } else {
+            this.startIndex = startIndex;
+            this.endIndex = endIndex;
+            this.buffer = buffer;
+        }
+    }
+
+    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) {
+        switch (type) {
+            case STRING:
+                return toEnum(cls, stringValue());
+            case INTEGER:
+                return toEnum(cls, intValue());
+            case NULL:
+                return null;
+        }
+        die("toEnum " + cls + " value was " + stringValue());
+        return null;
+    }
+
+    public static <T extends Enum> T toEnum(Class<T> cls, String value) {
+        try {
+            return (T) Enum.valueOf(cls, value);
+        } catch (Exception ex) {
+            return (T) Enum.valueOf(cls, value.toUpperCase().replace('-', '_'));
+        }
+    }
+
+    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 doubleValue();
+            case INTEGER:
+                if (isInteger(buffer, startIndex, endIndex - startIndex)) {
+                    return intValue();
+                } else {
+                    return longValue();
+                }
+            case STRING:
+                if (checkDate) {
+                    Date date = null;
+                    if (Dates.isISO8601QuickCheck(buffer, startIndex, endIndex)) {
+                        if (Dates.isJsonDate(buffer, startIndex, endIndex)) {
+                            date = Dates.fromJsonDate(buffer, startIndex, endIndex);
+                        } else if (Dates.isISO8601(buffer, startIndex, endIndex)) {
+                            date = Dates.fromISO8601(buffer, startIndex, endIndex);
+                        } else {
+                            return stringValue();
+                        }
+
+                        if (date == null) {
+                            return stringValue();
+                        } else {
+                            return date;
+                        }
+                    }
+                }
+                return stringValue();
+        }
+        die();
+        return null;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof Value)) return false;
+
+        CharSequenceValue value1 = (CharSequenceValue) 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 final int length() {
+        return buffer.length;
+    }
+
+    public final char charAt(int index) {
+        return buffer[index];
+    }
+
+    public final CharSequence subSequence(int start, int end) {
+        return new CharSequenceValue(false, type, start, end, buffer, decodeStrings, checkDate);
+    }
+
+    public BigDecimal bigDecimalValue() {
+        return new BigDecimal(buffer, startIndex, endIndex - startIndex);
+    }
+
+    public BigInteger bigIntegerValue() {
+        return new BigInteger(toString());
+    }
+
+    public String stringValue() {
+        if (this.decodeStrings) {
+            return JsonStringDecoder.decodeForSure(buffer, startIndex, endIndex);
+        } else {
+            return toString();
+        }
+    }
+
+    public String stringValueEncoded() {
+        return JsonStringDecoder.decode(buffer, startIndex, endIndex);
+    }
+
+    public Date dateValue() {
+        if (type == Type.STRING) {
+
+            if (Dates.isISO8601QuickCheck(buffer, startIndex, endIndex)) {
+
+                if (Dates.isJsonDate(buffer, startIndex, endIndex)) {
+                    return Dates.fromJsonDate(buffer, startIndex, endIndex);
+
+                } else if (Dates.isISO8601(buffer, startIndex, endIndex)) {
+                    return Dates.fromISO8601(buffer, startIndex, endIndex);
+                } else {
+                    throw new JsonException("Unable to convert " + stringValue() + " to date ");
+                }
+            } else {
+                throw new JsonException("Unable to convert " + stringValue() + " to date ");
+            }
+        } else {
+            return new Date(Dates.utc(longValue()));
+        }
+    }
+
+    public int intValue() {
+        int sign = 1;
+        if (buffer[startIndex] == '-') {
+            startIndex++;
+            sign = -1;
+        }
+        return parseIntFromTo(buffer, startIndex, endIndex) * sign;
+    }
+
+    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 CharScanner.parseDouble(this.buffer, startIndex, endIndex);
+    }
+
+    public boolean booleanValue() {
+        return Boolean.parseBoolean(toString());
+    }
+
+    public float floatValue() {
+        return CharScanner.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];
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/5a3f9996/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharacterSource.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharacterSource.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharacterSource.java
new file mode 100644
index 0000000..6fdecd4
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/CharacterSource.java
@@ -0,0 +1,81 @@
+/*
+ *  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 interface CharacterSource {
+
+    /**
+     * Skip white space.
+     */
+    void skipWhiteSpace();
+
+    /**
+     * returns the next character moving the file pointer or index to the next location.
+     */
+    int nextChar();
+
+    /**
+     * returns the current character without changing the IO pointer or index.
+     */
+    int currentChar();
+
+    /**
+     * Checks to see if there is a next character.
+     */
+    boolean hasChar();
+
+    /**
+     * Useful for finding constants in a string like true, false, etc.
+     */
+    boolean consumeIfMatch(char[] match);
+
+    /**
+     * This is mostly for debugging and testing.
+     */
+    int location();
+
+    /**
+     * Combines the operations of nextChar and hasChar.
+     * Characters is -1 if not found which signifies end of file.
+     * This might be preferable to avoid two method calls.
+     */
+    int safeNextChar();
+
+    /**
+     * Used to find strings and their ilk
+     * Finds the next non-escaped char
+     *
+     * @param ch  character to find
+     * @param esc escape character to avoid next char if escaped
+     * @return list of chars until this is found.
+     */
+    char[] findNextChar(int ch, int esc);
+
+    boolean hadEscape();
+
+    /**
+     * Reads a number from the character source.
+     */
+    char[] readNumber();
+
+    String errorDetails(String message);
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/5a3f9996/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Chr.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Chr.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Chr.java
new file mode 100644
index 0000000..6996a8e
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Chr.java
@@ -0,0 +1,213 @@
+/*
+ *  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 Rick Hightower
+ */
+public class Chr {
+
+    public static char[] array(final char... array) {
+        return array;
+    }
+
+    public static char[] chars(final String array) {
+        return array.toCharArray();
+    }
+
+    public static boolean in(char value, char[] array) {
+        for (char currentValue : array) {
+            if (currentValue == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean in(int value, char[] array) {
+        for (int currentValue : array) {
+            if (currentValue == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean in(char value, int offset, char[] array) {
+        for (int index = offset; index < array.length; index++) {
+            char currentValue = array[index];
+            if (currentValue == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean in(char value, int offset, int end, char[] array) {
+        for (int index = offset; index < end; index++) {
+            char currentValue = array[index];
+            if (currentValue == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static char[] grow(char[] array, final int size) {
+        char[] newArray = new char[array.length + size];
+        arraycopy(array, 0, newArray, 0, array.length);
+        return newArray;
+    }
+
+    public static char[] grow(char[] array) {
+        char[] newArray = new char[array.length * 2];
+        arraycopy(array, 0, newArray, 0, array.length);
+        return newArray;
+    }
+
+    public static char[] copy(char[] array) {
+        char[] newArray = new char[array.length];
+        arraycopy(array, 0, newArray, 0, array.length);
+        return newArray;
+    }
+
+    public static char[] copy(char[] array, int offset, int length) {
+        char[] newArray = new char[length];
+        arraycopy(array, offset, newArray, 0, length);
+        return newArray;
+    }
+
+    public static char[] add(char[] array, char v) {
+        char[] newArray = new char[array.length + 1];
+        arraycopy(array, 0, newArray, 0, array.length);
+        newArray[array.length] = v;
+        return newArray;
+    }
+
+    public static char[] add(char[] array, String str) {
+        return add(array, str.toCharArray());
+    }
+
+    public static char[] add(char[] array, StringBuilder stringBuilder) {
+        return add(array, getCharsFromStringBuilder(stringBuilder));
+    }
+
+    public static char[] add(char[] array, char[] array2) {
+        char[] newArray = new char[array.length + array2.length];
+        arraycopy(array, 0, newArray, 0, array.length);
+        arraycopy(array2, 0, newArray, array.length, array2.length);
+        return newArray;
+    }
+
+    /* End universal methods. */
+
+    private static char[] getCharsFromStringBuilder(StringBuilder sbuf) {
+        final int length = sbuf.length();
+        char[] array2 = new char[length];
+        sbuf.getChars(0, length, array2, 0);
+        return array2;
+    }
+
+    public static char[] lpad(final char[] in, final int size, char pad) {
+        if (in.length >= size) {
+            return in;
+        }
+
+        int delta = size - in.length;
+        int index = 0;
+        char[] newArray = new char[size];
+
+        for (; index < delta; index++) {
+            newArray[index] = pad;
+        }
+
+        for (int index2 = 0; index2 < in.length; index++, index2++) {
+            newArray[index] = in[index2];
+        }
+
+        return newArray;
+    }
+
+    public static boolean contains(char[] chars, char c, int start, final int length) {
+        final int to = length + start;
+        for (int index = start; index < to; index++) {
+            char ch = chars[index];
+            if (ch == c) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static void _idx(char[] buffer, int location, byte[] chars) {
+        int index2 = 0;
+        int endLocation = (location + chars.length);
+        for (int index = location; index < endLocation; index++, index2++) {
+            buffer[index] = (char) chars[index2];
+        }
+    }
+
+    public static void _idx(final char[] array, int startIndex, char[] input) {
+        try {
+            arraycopy(input, 0, array, startIndex, input.length);
+        } catch (Exception ex) {
+            Exceptions.handle(String.format("array size %d, startIndex %d, input length %d",
+                    array.length, startIndex, input.length), ex);
+        }
+    }
+
+    private static void arraycopy(final char[] src, final int srcPos, final char[] dest, final int destPos, final int length) {
+        System.arraycopy(src, srcPos, dest, destPos, length);
+    }
+
+    public static void _idx(final char[] array, int startIndex, char[] input, final int inputLength) {
+        try {
+            arraycopy(input, 0, array, startIndex, inputLength);
+        } catch (Exception ex) {
+            Exceptions.handle(String.format("array size %d, startIndex %d, input length %d",
+                    array.length, startIndex, input.length), ex);
+        }
+    }
+
+    public static void _idx(char[] buffer, int location, byte[] chars, int start, int end) {
+        int index2 = start;
+        int endLocation = (location + (end - start));
+        for (int index = location; index < endLocation; index++, index2++) {
+            buffer[index] = (char) chars[index2];
+        }
+    }
+
+    public static char[] add(char[]... strings) {
+        int length = 0;
+        for (char[] str : strings) {
+            if (str == null) {
+                continue;
+            }
+            length += str.length;
+        }
+        CharBuf builder = CharBuf.createExact(length);
+        for (char[] str : strings) {
+            if (str == null) {
+                continue;
+            }
+            builder.add(str);
+        }
+        return builder.toCharArray();
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/5a3f9996/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Dates.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Dates.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Dates.java
new file mode 100644
index 0000000..0cd95f8
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Dates.java
@@ -0,0 +1,184 @@
+/*
+ *  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.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * @author Rick Hightower
+ */
+public class Dates {
+
+    private static TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");
+
+    public static long utc(long time) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(time);
+        calendar.setTimeZone(UTC_TIME_ZONE);
+        return calendar.getTime().getTime();
+    }
+
+    private static Date internalDate(TimeZone tz, int year, int month, int day, int hour,
+                                     int minute, int second, int miliseconds) {
+
+        Calendar calendar = Calendar.getInstance();
+
+        calendar.set(Calendar.YEAR, year);
+        calendar.set(Calendar.MONTH, month - 1);
+        calendar.set(Calendar.DAY_OF_MONTH, day);
+        calendar.set(Calendar.HOUR_OF_DAY, hour);
+        calendar.set(Calendar.MINUTE, minute);
+        calendar.set(Calendar.SECOND, second);
+        calendar.set(Calendar.MILLISECOND, miliseconds);
+
+        calendar.setTimeZone(tz);
+
+        return calendar.getTime();
+    }
+
+    public static Date toDate(TimeZone tz, int year, int month, int day,
+                              int hour, int minute, int second) {
+        return internalDate(tz, year, month, day, hour, minute, second, 0);
+    }
+
+    public static Date toDate(TimeZone tz, int year, int month, int day,
+                              int hour, int minute, int second, int miliseconds) {
+        return internalDate(tz, year, month, day, hour, minute, second, miliseconds);
+    }
+
+    static final int SHORT_ISO_8601_TIME_LENGTH = "1994-11-05T08:15:30Z".length();
+    static final int LONG_ISO_8601_TIME_LENGTH = "1994-11-05T08:15:30-05:00".length();
+    public static final int JSON_TIME_LENGTH = "2013-12-14T01:55:33.412Z".length();
+
+    public static Date fromISO8601(char[] charArray, int from, int to) {
+        try {
+            if (isISO8601(charArray, from, to)) {
+                int year = CharScanner.parseIntFromTo(charArray, from, from + 4);
+                int month = CharScanner.parseIntFromTo(charArray, from + 5, from + 7);
+                int day = CharScanner.parseIntFromTo(charArray, from + 8, from + 10);
+                int hour = CharScanner.parseIntFromTo(charArray, from + 11, from + 13);
+
+                int minute = CharScanner.parseIntFromTo(charArray, from + 14, from + 16);
+
+                int second = CharScanner.parseIntFromTo(charArray, from + 17, from + 19);
+                TimeZone tz = null;
+
+                if (charArray[from + 19] == 'Z') {
+                    tz = TimeZone.getTimeZone("GMT");
+                } else {
+                    String tzStr = "GMT" + String.valueOf(charArray, from + 19, 6);
+                    tz = TimeZone.getTimeZone(tzStr);
+                }
+                return toDate(tz, year, month, day, hour, minute, second);
+            } else {
+                return null;
+            }
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    public static Date fromJsonDate(char[] charArray, int from, int to) {
+        try {
+            if (isJsonDate(charArray, from, to)) {
+                int year = CharScanner.parseIntFromTo(charArray, from, from + 4);
+                int month = CharScanner.parseIntFromTo(charArray, from + 5, from + 7);
+                int day = CharScanner.parseIntFromTo(charArray, from + 8, from + 10);
+                int hour = CharScanner.parseIntFromTo(charArray, from + 11, from + 13);
+
+                int minute = CharScanner.parseIntFromTo(charArray, from + 14, from + 16);
+
+                int second = CharScanner.parseIntFromTo(charArray, from + 17, from + 19);
+
+                int miliseconds = CharScanner.parseIntFromTo(charArray, from + 20, from + 23);
+
+                TimeZone tz = TimeZone.getTimeZone("GMT");
+
+                return toDate(tz, year, month, day, hour, minute, second, miliseconds);
+            } else {
+                return null;
+            }
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    public static boolean isISO8601(char[] charArray, int start, int to) {
+        boolean valid = true;
+        final int length = to - start;
+
+        if (length == SHORT_ISO_8601_TIME_LENGTH) {
+            valid &= (charArray[start + 19] == 'Z');
+        } else if (length == LONG_ISO_8601_TIME_LENGTH) {
+            valid &= (charArray[start + 19] == '-' || charArray[start + 19] == '+');
+            valid &= (charArray[start + 22] == ':');
+        } else {
+            return false;
+        }
+
+        //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
+        // "1 9 9 4 - 1 1 - 0 5 T 0 8 : 1 5 : 3 0 - 0 5 : 0 0
+
+        valid &= (charArray[start + 4] == '-') &&
+                (charArray[start + 7] == '-') &&
+                (charArray[start + 10] == 'T') &&
+                (charArray[start + 13] == ':') &&
+                (charArray[start + 16] == ':');
+
+        return valid;
+    }
+
+    public static boolean isISO8601QuickCheck(char[] charArray, int start, int to) {
+        final int length = to - start;
+
+        try {
+            return length == JSON_TIME_LENGTH || length == LONG_ISO_8601_TIME_LENGTH
+                    || length == SHORT_ISO_8601_TIME_LENGTH || (length >= 17 && (charArray[start + 16] == ':'));
+
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            return false;
+        }
+    }
+
+    public static boolean isJsonDate(char[] charArray, int start, int to) {
+        boolean valid = true;
+        final int length = to - start;
+
+        if (length != JSON_TIME_LENGTH) {
+            return false;
+        }
+
+        valid &= (charArray[start + 19] == '.' || charArray[start + 19] == '+');
+
+        if (!valid) {
+            return false;
+        }
+
+        valid &= (charArray[start + 4] == '-') &&
+                (charArray[start + 7] == '-') &&
+                (charArray[start + 10] == 'T') &&
+                (charArray[start + 13] == ':') &&
+                (charArray[start + 16] == ':');
+
+        return valid;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/5a3f9996/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Exceptions.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Exceptions.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Exceptions.java
new file mode 100644
index 0000000..e122778
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/Exceptions.java
@@ -0,0 +1,178 @@
+/*
+ *  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.PrintStream;
+import java.io.PrintWriter;
+import java.util.Collections;
+
+/**
+ * @author Rick Hightower
+ */
+public class Exceptions {
+
+    public static boolean die() {
+        throw new JsonInternalException("died");
+    }
+
+    public static boolean die(String message) {
+        throw new JsonInternalException(message);
+    }
+
+    public static <T> T die(Class<T> clazz, String message) {
+        throw new JsonInternalException(message);
+    }
+
+    public static void handle(java.lang.Exception e) {
+        throw new JsonInternalException(e);
+    }
+
+    public static <T> T handle(Class<T> clazz, java.lang.Exception e) {
+        if (e instanceof JsonInternalException) {
+            throw (JsonInternalException) e;
+        }
+        throw new JsonInternalException(e);
+    }
+
+    public static <T> T handle(Class<T> clazz, String message, Throwable e) {
+        throw new JsonInternalException(message, e);
+    }
+
+    public static void handle(String message, Throwable e) {
+        throw new JsonInternalException(message, e);
+    }
+
+    public static class JsonInternalException extends JsonException {
+
+        public JsonInternalException(String message) {
+            super(message);
+        }
+
+        public JsonInternalException(String message, Throwable cause) {
+            super(message, cause);
+        }
+
+        public JsonInternalException(Throwable cause) {
+            super("Wrapped Exception", cause);
+        }
+
+        public void printStackTrace(PrintStream s) {
+            s.println(this.getMessage());
+            if (getCause() != null) {
+                s.println("This Exception was wrapped, the original exception\n" +
+                        "stack trace is:\n");
+                getCause().printStackTrace(s);
+            } else {
+                super.printStackTrace(s);
+            }
+        }
+
+        public String getMessage() {
+            return super.getMessage() + (getCause() == null ? "" :
+                    getCauseMessage());
+        }
+
+        private String getCauseMessage() {
+            return "\n CAUSE " + getCause().getClass().getName() + " :: " +
+                    getCause().getMessage();
+        }
+
+        public String getLocalizedMessage() {
+            return this.getMessage();
+        }
+
+        public StackTraceElement[] getStackTrace() {
+            if (getCause() != null) {
+                return getCause().getStackTrace();
+            } else {
+                return super.getStackTrace();
+            }
+        }
+
+        public Throwable getCause() {
+            return super.getCause();
+        }
+
+        public void printStackTrace(PrintWriter s) {
+            s.println(this.getMessage());
+
+            if (getCause() != null) {
+                s.println("This Exception was wrapped, the original exception\n" +
+                        "stack trace is:\n");
+                getCause().printStackTrace(s);
+            } else {
+                super.printStackTrace(s);
+            }
+        }
+
+        public void printStackTrace() {
+            System.err.println(this.getMessage());
+
+            if (getCause() != null) {
+                System.err.println("This Exception was wrapped, the original exception\n" +
+                        "stack trace is:\n");
+                getCause().printStackTrace();
+            } else {
+                super.printStackTrace();
+            }
+        }
+    }
+
+    public static String toString(Exception ex) {
+        CharBuf buffer = CharBuf.create(255);
+        buffer.addLine(ex.getLocalizedMessage());
+
+        final StackTraceElement[] stackTrace = ex.getStackTrace();
+        for (StackTraceElement element : stackTrace) {
+            buffer.add(element.getClassName());
+            sputs(buffer, "class", element.getClassName(),
+                    "method", element.getMethodName(), "line", element.getLineNumber());
+        }
+
+        return buffer.toString();
+    }
+
+    public static String sputs(CharBuf buf, Object... messages) {
+        int index = 0;
+        for (Object message : messages) {
+            if (index != 0) {
+                buf.add(' ');
+            }
+            index++;
+
+            if (message == null) {
+                buf.add("<NULL>");
+            } else if (message.getClass().isArray()) {
+                buf.add(Collections.singletonList(message).toString());
+            } else {
+                buf.add(message.toString());
+            }
+        }
+        buf.add('\n');
+
+        return buf.toString();
+    }
+
+    public static String sputs(Object... messages) {
+        CharBuf buf = CharBuf.create(100);
+        return sputs(buf, messages);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/5a3f9996/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/FastStringUtils.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/FastStringUtils.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/FastStringUtils.java
new file mode 100644
index 0000000..a8c5dc6
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/FastStringUtils.java
@@ -0,0 +1,85 @@
+/*
+ *  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 org.apache.groovy.json.DefaultFastStringService;
+import org.apache.groovy.json.FastStringService;
+import org.apache.groovy.json.FastStringServiceFactory;
+
+import java.util.ServiceLoader;
+
+/**
+ * Internal class for fast processing of Strings during JSON parsing
+ */
+public class FastStringUtils {
+
+    private static class ServiceHolder {
+        static final FastStringService INSTANCE = loadService();
+
+        private static FastStringService loadService() {
+            ClassLoader loader = Thread.currentThread().getContextClassLoader();
+// left classloading very simple in light of potential changes needed for jdk9
+// that means you might need @GrabConfig(systemClassLoader=true) if getting json via grab
+//            ClassLoader rootLoader = DefaultGroovyMethods.getRootLoader(loader);
+            ServiceLoader<FastStringServiceFactory> serviceLoader = ServiceLoader.load(FastStringServiceFactory.class, loader);
+            FastStringService found = null;
+            for (FastStringServiceFactory factory : serviceLoader) {
+                FastStringService service = factory.getService();
+                if (service != null) {
+                    found = service;
+                    if (!(service instanceof DefaultFastStringService)) {
+                        break;
+                    }
+                }
+            }
+            return found;
+        }
+    }
+
+    private static FastStringService getService() {
+        if (ServiceHolder.INSTANCE == null) {
+            throw new RuntimeException("Unable to load FastStringService");
+        }
+        return ServiceHolder.INSTANCE;
+    }
+
+    /**
+     * @param string string to grab array from.
+     * @return char array from string
+     */
+    public static char[] toCharArray(final String string) {
+        return getService().toCharArray(string);
+    }
+
+    /**
+     * @param charSequence to grab array from.
+     * @return char array from char sequence
+     */
+    public static char[] toCharArray(final CharSequence charSequence) {
+        return toCharArray(charSequence.toString());
+    }
+
+    /**
+     * @param chars to shove array into.
+     * @return new string with chars copied into it
+     */
+    public static String noCopyStringFromChars(final char[] chars) {
+        return getService().noCopyStringFromChars(chars);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/5a3f9996/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/IO.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/IO.java b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/IO.java
new file mode 100644
index 0000000..fdfec3e
--- /dev/null
+++ b/subprojects/groovy-json/src/main/java/org/apache/groovy/json/internal/IO.java
@@ -0,0 +1,91 @@
+/*
+ *  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.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+/**
+ * @author Rick Hightower
+ */
+public class IO {
+
+    private static final int EOF = -1;
+
+    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+    public static CharBuf read(Reader input, CharBuf charBuf, final int bufSize) {
+        if (charBuf == null) {
+            charBuf = CharBuf.create(bufSize);
+        } else {
+            charBuf.readForRecycle();
+        }
+
+        try {
+            char[] buffer = charBuf.toCharArray();
+            int size = input.read(buffer);
+            if (size != -1) {
+                charBuf._len(size);
+            }
+            if (size < 0) {
+                return charBuf;
+            }
+
+            copy(input, charBuf);
+        } catch (IOException e) {
+            Exceptions.handle(e);
+        } finally {
+            try {
+                input.close();
+            } catch (IOException e) {
+                Exceptions.handle(e);
+            }
+        }
+
+        return charBuf;
+    }
+
+    public static int copy(Reader input, Writer output) {
+        long count = copyLarge(input, output);
+        if (count > Integer.MAX_VALUE) {
+            return -1;
+        }
+        return (int) count;
+    }
+
+    public static long copyLarge(Reader reader, Writer writer) {
+        return copyLarge(reader, writer, new char[DEFAULT_BUFFER_SIZE]);
+    }
+
+    public static long copyLarge(Reader reader, Writer writer, char[] buffer) {
+        long count = 0;
+        int n;
+
+        try {
+            while (EOF != (n = reader.read(buffer))) {
+                writer.write(buffer, 0, n);
+                count += n;
+            }
+        } catch (IOException e) {
+            Exceptions.handle(e);
+        }
+        return count;
+    }
+}