svn commit: r908712 - in /ofbiz/trunk/framework/base: build.xml src/org/ofbiz/base/json/ src/org/ofbiz/base/json/JSON.jj src/org/ofbiz/base/json/JSONWriter.java src/org/ofbiz/base/json/test/ src/org/ofbiz/base/json/test/JSONTests.java

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r908712 - in /ofbiz/trunk/framework/base: build.xml src/org/ofbiz/base/json/ src/org/ofbiz/base/json/JSON.jj src/org/ofbiz/base/json/JSONWriter.java src/org/ofbiz/base/json/test/ src/org/ofbiz/base/json/test/JSONTests.java

doogie-3
Author: doogie
Date: Wed Feb 10 22:47:30 2010
New Revision: 908712

URL: http://svn.apache.org/viewvc?rev=908712&view=rev
Log:
JSON reading/writing.  May seem like it is uneeded, but a later commit
will add support for this becoming more extensible.

Added:
    ofbiz/trunk/framework/base/src/org/ofbiz/base/json/
    ofbiz/trunk/framework/base/src/org/ofbiz/base/json/JSON.jj
    ofbiz/trunk/framework/base/src/org/ofbiz/base/json/JSONWriter.java
    ofbiz/trunk/framework/base/src/org/ofbiz/base/json/test/
    ofbiz/trunk/framework/base/src/org/ofbiz/base/json/test/JSONTests.java
Modified:
    ofbiz/trunk/framework/base/build.xml

Modified: ofbiz/trunk/framework/base/build.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/build.xml?rev=908712&r1=908711&r2=908712&view=diff
==============================================================================
--- ofbiz/trunk/framework/base/build.xml (original)
+++ ofbiz/trunk/framework/base/build.xml Wed Feb 10 22:47:30 2010
@@ -39,6 +39,7 @@
 
     <filelist id="test.classes" dir="${src.dir}">
         <file name="org/ofbiz/base/util/test/IndentingWriterTests.java"/>
+        <file name="org/ofbiz/base/json/test/JSONTests.java"/>
         <file name="org/ofbiz/base/conversion/test/MiscTests.java"/>
         <file name="org/ofbiz/base/util/test/UtilIOTests.java"/>
         <file name="org/ofbiz/base/test/BaseUnitTests.java"/>
@@ -66,6 +67,14 @@
         </patternset>
     </target>
 
+    <target name="gen-src">
+        <ofbiz-javacc dir="org/ofbiz/base/json" file="JSON"/>
+    </target>
+
+    <target name="classes" depends="prepare,gen-src">
+        <javac16/>
+    </target>
+
     <target name="jar" depends="classes">
         <jar jarfile="${build.dir}/lib/${name}.jar">
             <fileset dir="${build.dir}/classes"/>

Added: ofbiz/trunk/framework/base/src/org/ofbiz/base/json/JSON.jj
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/src/org/ofbiz/base/json/JSON.jj?rev=908712&view=auto
==============================================================================
--- ofbiz/trunk/framework/base/src/org/ofbiz/base/json/JSON.jj (added)
+++ ofbiz/trunk/framework/base/src/org/ofbiz/base/json/JSON.jj Wed Feb 10 22:47:30 2010
@@ -0,0 +1,320 @@
+/*******************************************************************************
+ * 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.
+ *******************************************************************************/
+options {
+  ERROR_REPORTING = true;
+  STATIC = false;
+//  DEBUG_PARSER = true;
+//  DEBUG_LOOKAHEAD = true;
+//  DEBUG_TOKEN_MANAGER = true;
+  LOOKAHEAD = 1;
+//  CHOICE_AMBIGUITY_CHECK = 3;
+//  OTHER_AMBIGUITY_CHECK = 3;
+}
+
+PARSER_BEGIN(JSON)
+
+package org.ofbiz.base.json;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javolution.util.FastMap;
+
+import org.ofbiz.base.util.UtilIO;
+
+public class JSON {
+    private boolean allowResolve;
+
+    public JSON(String fileName) {
+        this(System.in);
+        try { ReInit(new FileInputStream(new File(fileName))); }
+        catch(Exception e) { e.printStackTrace(); }
+//        allowed[TEXT] = 1;
+//        allowed[FIRST_COLUMN_TEXT] = 1;
+    }
+
+    public JSON allowResolve(boolean allowResolve) {
+        this.allowResolve = allowResolve;
+ token_source.SwitchTo(allowResolve ? RESOLVE : DEFAULT);
+ return this;
+    }
+}
+
+PARSER_END(JSON)
+TOKEN_MGR_DECLS: {
+    protected LinkedList stateStack = new LinkedList();
+    protected StringBuilder statePrefix = new StringBuilder();
+
+    protected void pushState(int state) {
+        //System.err.println(statePrefix + "push: " + lexStateNames[curLexState] + " -> " + lexStateNames[state]);
+        stateStack.add(curLexState);
+        statePrefix.append(' ');
+        SwitchTo(state);
+    }
+
+    protected void popState() {
+        int oldState = (Integer) stateStack.removeLast();
+        statePrefix.setLength(statePrefix.length() - 1);
+        //System.err.println(statePrefix + "pop: " + lexStateNames[curLexState] + " -> " + lexStateNames[oldState]);
+        SwitchTo(oldState);
+    }
+}
+<DEFAULT,RESOLVE>
+TOKEN:
+{
+  <OBJECT_BEGIN: "{">
+| <OBJECT_END: "}">
+| <ARRAY_BEGIN: "[">
+| <ARRAY_END: "]">
+| <ITEM_SEP: ",">
+| <KEY_SEP: ":">
+| <TRUE: "true">
+| <FALSE: "false">
+| <NULL: "null">
+| <WHOLE_NUMBER:
+    <NUMBER_PREFIX>
+  >
+| <FLOAT_NUMBER:
+    <NUMBER_PREFIX>
+    ("." (["0"-"9"])*)?
+    (
+      ["e", "E"]
+      (["+", "-"])?
+      (["0"-"9"])+
+    )?
+  >
+| <#NUMBER_PREFIX:
+    (["-"])?
+    (
+      "0"
+    | ["1"-"9"] (["0"-"9"])*
+    )
+  >
+}
+
+<DEFAULT,RESOLVE,IN_RESOLVE_VALUE>
+TOKEN:
+{
+  <STRING_START: "\""> { pushState(IN_STRING); }
+}
+
+<IN_RESOLVE_VALUE>
+TOKEN:
+{
+  <CLOSE_PAREN: ")"> { popState(); }
+}
+
+<RESOLVE>
+TOKEN:
+{
+  <RESOLVE_BEGIN: "resolve("> { pushState(IN_RESOLVE_VALUE); }
+}
+
+<IN_STRING>
+TOKEN:
+{
+  <CHARACTER: (~["\"","\\","\u0000"-"\u001f"])+>
+| <STRING_END: "\""> { popState(); }
+| <CONTROL_CHAR: "\\" ["\"", "\\", "/", "b","f","n","r","t"]>
+| <UNICODE: "\\u"
+    ["a"-"f", "A"-"F", "0"-"9"]
+    ["a"-"f", "A"-"F", "0"-"9"]
+    ["a"-"f", "A"-"F", "0"-"9"]
+    ["a"-"f", "A"-"F", "0"-"9"]
+  >
+}
+
+<DEFAULT,RESOLVE>
+SKIP:
+{
+  <WHITESPACE: [" ", "\r", "\n", "\t"]>
+}
+
+<*>
+MORE:
+{
+  <COMMENT_START: "/*"> { if (curLexState != IN_COMMENT) pushState(IN_COMMENT); }
+}
+
+<IN_COMMENT>
+MORE:
+{
+  <(~[])>
+}
+
+<IN_COMMENT>
+MORE:
+{
+  <COMMENT_END: "*/"> { popState(); }
+}
+
+Object JSONValue():
+{ Object value; }
+{
+  value=JSONItem()
+  <EOF>
+  { return value; }
+}
+
+Object JSONItem():
+{ Object value; }
+{
+  (
+    value=JSONString()
+  | value=JSONLong()
+  | value=JSONFloat()
+  | value=JSONObject()
+  | value=JSONArray()
+  | value=True()
+  | value=False()
+  | value=Null()
+  | value=JSONResolve()
+  )
+  { return value; }
+}
+
+Object JSONResolve():
+{
+  String name, value;
+}
+{
+  <RESOLVE_BEGIN>
+  value=JSONString()
+  <CLOSE_PAREN>
+  {
+    try {
+      return UtilIO.readObject(value.toCharArray());
+    } catch (ClassNotFoundException e) {
+      throw generateParseException();
+    } catch (IOException e) {
+      throw generateParseException();
+    }
+  }
+}
+String JSONString():
+{
+  StringBuilder sb = new StringBuilder();
+}
+{
+  <STRING_START> (
+    <CHARACTER> { sb.append(getToken(0).image); }
+  | <CONTROL_CHAR> {
+    switch (getToken(0).image.charAt(1)) {
+      case '"': sb.append('"'); break;
+      case '\\': sb.append('\\'); break;
+      case '/': sb.append('/'); break;
+      case 'b': sb.append('\b'); break;
+      case 'f': sb.append('\f'); break;
+      case 'n': sb.append('\n'); break;
+      case 'r': sb.append('\r'); break;
+      case 't': sb.append('\t'); break;
+    }
+  }
+  | <UNICODE> {
+    int v = Integer.parseInt(getToken(0).image.substring(2), 16);
+    sb.append((char) v);
+  }
+  )*
+  <STRING_END>
+  { return sb.toString(); }
+}
+
+Long JSONLong():
+{}
+{
+  try {
+    <WHOLE_NUMBER> { return new Long(getToken(0).image); }
+  } catch (NumberFormatException e) {
+    throw generateParseException();
+  }
+}
+
+Double JSONFloat():
+{}
+{
+  try {
+    <FLOAT_NUMBER> { return new Double(getToken(0).image); }
+  } catch (NumberFormatException e) {
+    throw generateParseException();
+  }
+}
+
+Map<String, Object> JSONObject():
+{
+  Map<String, Object> map = FastMap.newInstance();
+}
+{
+  <OBJECT_BEGIN>
+  (<ITEM_SEP>)*
+  (JSONObjectEntry(map))? (<ITEM_SEP> (JSONObjectEntry(map))?)*
+  <OBJECT_END>
+  { return map; }
+}
+
+void JSONObjectEntry(Map<String, Object> map):
+{
+  String key;
+  Object value;
+}
+{
+  key=JSONString() <KEY_SEP> value=JSONItem()
+  { map.put(key, value); }
+}
+
+List JSONArray():
+{
+  ArrayList<Object> list = new ArrayList<Object>();
+  Object value;
+}
+{
+  <ARRAY_BEGIN>
+  (<ITEM_SEP>)*
+  (
+    value=JSONItem() { list.add(value); }
+    (
+      <ITEM_SEP>
+      (value=JSONItem() { list.add(value); })?
+    )*
+  )?
+  <ARRAY_END>
+  { return list; }
+}
+
+Boolean True():
+{}
+{
+  <TRUE> { return Boolean.TRUE; }
+}
+
+Boolean False():
+{}
+{
+  <FALSE> { return Boolean.FALSE; }
+}
+
+Object Null():
+{}
+{
+  <NULL> { return null; }
+}

Added: ofbiz/trunk/framework/base/src/org/ofbiz/base/json/JSONWriter.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/src/org/ofbiz/base/json/JSONWriter.java?rev=908712&view=auto
==============================================================================
--- ofbiz/trunk/framework/base/src/org/ofbiz/base/json/JSONWriter.java (added)
+++ ofbiz/trunk/framework/base/src/org/ofbiz/base/json/JSONWriter.java Wed Feb 10 22:47:30 2010
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * 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.ofbiz.base.json;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.ofbiz.base.util.IndentingWriter;
+import org.ofbiz.base.util.UtilIO;
+
+public class JSONWriter {
+    private final IndentingWriter writer;
+    private final FallbackHandler fallbackHandler;
+
+    public JSONWriter(IndentingWriter writer) {
+        this(writer, StandardFallbackHandler);
+    }
+
+    public JSONWriter(IndentingWriter writer, FallbackHandler fallbackHandler) {
+        this.writer = writer;
+        this.fallbackHandler = fallbackHandler;
+    }
+
+    public JSONWriter(Writer writer) {
+        this(writer instanceof IndentingWriter ? (IndentingWriter) writer : new IndentingWriter(writer));
+    }
+
+    public JSONWriter(Writer writer, FallbackHandler fallbackHandler) {
+        this(writer instanceof IndentingWriter ? (IndentingWriter) writer : new IndentingWriter(writer), fallbackHandler);
+    }
+
+    public IndentingWriter getWriter() {
+        return writer;
+    }
+
+    public JSONWriter close() throws IOException {
+        getWriter().close();
+        return this;
+    }
+
+    public JSONWriter write(byte b) throws IOException {
+        writer.write(Byte.toString(b));
+        return this;
+    }
+
+    public JSONWriter write(short s) throws IOException {
+        writer.write(Short.toString(s));
+        return this;
+    }
+
+    public JSONWriter write(int i) throws IOException {
+        writer.write(Integer.toString(i));
+        return this;
+    }
+
+    public JSONWriter write(long l) throws IOException {
+        writer.write(Long.toString(l));
+        return this;
+    }
+
+    public JSONWriter write(float f) throws IOException {
+        writer.write(Float.toString(f));
+        return this;
+    }
+
+    public JSONWriter write(double d) throws IOException {
+        writer.write(Double.toString(d));
+        return this;
+    }
+
+    public JSONWriter write(char c) throws IOException {
+        write(Character.toString(c));
+        return this;
+    }
+
+    public JSONWriter write(String s) throws IOException {
+        writer.write('"');
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            switch (c) {
+                case '\\':  writer.write("\\\\"); continue;
+                case '/':  writer.write("\\/"); continue;
+                case '"':  writer.write("\\\""); continue;
+                case '\b':  writer.write("\\b"); continue;
+                case '\f':  writer.write("\\f"); continue;
+                case '\n':  writer.write("\\n"); continue;
+                case '\r':  writer.write("\\r"); continue;
+                case '\t':  writer.write("\\t"); continue;
+            }
+            if (32 <= c && c >= 256) {
+                writer.write("\\u");
+                String n = Integer.toString((int) c, 16);
+                for (int j = 4 - n.length(); j > 0; j--) writer.write('0');
+                writer.write(n);
+            } else {
+                writer.write(c);
+            }
+        }
+        writer.write('"');
+        return this;
+    }
+
+    public <K, V> JSONWriter write(Map<K, V> m) throws IOException {
+        writer.write('{');
+        writer.push();
+        Iterator<Map.Entry<K, V>> it = m.entrySet().iterator();
+        if (it.hasNext()) writer.newline();
+        while (it.hasNext()) {
+            Map.Entry<K, V> entry = it.next();
+            write(entry.getKey());
+            writer.write(':');
+            writer.space();
+            write(entry.getValue());
+            if (it.hasNext()) writer.write(',');
+            writer.newline();
+        }
+        writer.pop();
+        writer.write('}');
+        return this;
+    }
+
+    public <E> JSONWriter write(Collection<E> c) throws IOException {
+        writer.write('[');
+        writer.push();
+        Iterator<E> it = c.iterator();
+        if (it.hasNext()) writer.newline();
+        while (it.hasNext()) {
+            write(it.next());
+            if (it.hasNext()) writer.write(',');
+            writer.newline();
+        }
+        writer.pop();
+        writer.write(']');
+        return this;
+    }
+
+    public <T> JSONWriter write(T... o) throws IOException {
+        writer.write('[');
+        writer.push();
+        for (int i = 0; i < o.length; i++) {
+            if (i != 0) writer.write(',');
+            writer.newline();
+            write(o[i]);
+        }
+        if (o.length > 0) writer.newline();
+        writer.pop();
+        writer.write(']');
+        return this;
+    }
+
+    public JSONWriter write(Object o) throws IOException {
+        if (o == null) {
+            writer.write("null");
+            return this;
+        } else if (o instanceof Boolean) {
+            writer.write(((Boolean) o).booleanValue() ? "true" : "false");
+            return this;
+        } else if (o instanceof String) {
+            return write((String) o);
+        } else if (o instanceof Map) {
+            return write((Map) o);
+        } else if (o instanceof Collection) {
+            return write((Collection) o);
+        } else if (o instanceof Byte) {
+            return write(((Byte) o).byteValue());
+        } else if (o instanceof Character) {
+            return write(((Character) o).charValue());
+        } else if (o instanceof Double) {
+            return write(((Double) o).doubleValue());
+        } else if (o instanceof Float) {
+            return write(((Float) o).floatValue());
+        } else if (o instanceof Integer) {
+            return write(((Integer) o).intValue());
+        } else if (o instanceof Long) {
+            return write(((Long) o).longValue());
+        } else if (o instanceof Short) {
+            return write(((Short) o).shortValue());
+        } else if (o.getClass().isArray()) {
+            return write((Object[]) o);
+        } else {
+            fallbackHandler.writeJSON(this, writer, o);
+            return this;
+        }
+    }
+
+    public interface FallbackHandler {
+        void writeJSON(JSONWriter json, Writer writer, Object o) throws IOException;
+    }
+
+    public static final FallbackHandler StandardFallbackHandler = new FallbackHandler() {
+        public void writeJSON(JSONWriter json, Writer writer, Object o) throws IOException {
+            throw new IOException("Can't write(" + o + ":" + o.getClass() + ")");
+        }
+    };
+
+    public static final FallbackHandler ResolvingFallbackHandler = new FallbackHandler() {
+        public void writeJSON(JSONWriter json, Writer writer, Object o) throws IOException {
+            StringBuilder sb = new StringBuilder();
+            UtilIO.writeObject(sb, o);
+            writer.write("resolve(");
+            json.write(sb.toString());
+            writer.write(")");
+        }
+    };
+}

Added: ofbiz/trunk/framework/base/src/org/ofbiz/base/json/test/JSONTests.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/src/org/ofbiz/base/json/test/JSONTests.java?rev=908712&view=auto
==============================================================================
--- ofbiz/trunk/framework/base/src/org/ofbiz/base/json/test/JSONTests.java (added)
+++ ofbiz/trunk/framework/base/src/org/ofbiz/base/json/test/JSONTests.java Wed Feb 10 22:47:30 2010
@@ -0,0 +1,289 @@
+/*******************************************************************************
+ * 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.ofbiz.base.json.test;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.ofbiz.base.json.JSON;
+import org.ofbiz.base.json.JSONConstants;
+import org.ofbiz.base.json.JSONWriter;
+import org.ofbiz.base.json.ParseException;
+import org.ofbiz.base.json.Token;
+import org.ofbiz.base.json.TokenMgrError;
+import org.ofbiz.base.test.GenericTestCaseBase;
+
+public class JSONTests extends GenericTestCaseBase {
+    public JSONTests(String name) {
+        super(name);
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    protected Object parseJSON(String value, boolean allowResolve) throws Exception {
+        return new JSON(new StringReader(value)).allowResolve(allowResolve).JSONValue();
+    }
+
+    protected String getJSON(Object object, boolean allowResolve) throws Exception {
+        StringWriter writer = new StringWriter();
+        JSONWriter jsonWriter;
+        if (allowResolve) {
+            jsonWriter = new JSONWriter(writer, JSONWriter.ResolvingFallbackHandler);
+        } else {
+            jsonWriter = new JSONWriter(writer);
+        };
+        jsonWriter.write(object);
+        return writer.toString();
+    }
+
+    protected void assertSimpleJSONByte(byte n, String json) throws Exception {
+        assertSimpleJSON("integer - byte", new Byte(n), json, new Long(n));
+        assertSimpleJSONShort((short) n, json);
+    }
+
+    protected void assertSimpleJSONShort(short n, String json) throws Exception {
+        assertSimpleJSON("integer - short", new Integer(n), json, new Long(n));
+        assertSimpleJSONInteger((int) n, json);
+    }
+
+    protected void assertSimpleJSONInteger(int n, String json) throws Exception {
+        assertSimpleJSON("integer - int", new Short((short) n), json, new Long(n));
+        assertSimpleJSONLong((long) n, json);
+    }
+
+    protected void assertSimpleJSONLong(long n, String json) throws Exception {
+        assertSimpleJSON("integer - long", new Long(n), json, new Long(n));
+    }
+
+    protected void assertSimpleJSONFloat(float n, String json) throws Exception {
+        assertSimpleJSON("float - float", new Float(n), json, new Double(n));
+        assertSimpleJSONDouble((double) n, json);
+    }
+
+    protected void assertSimpleJSONDouble(double n, String json) throws Exception {
+        assertSimpleJSON("float - double", new Double(n), json);
+    }
+
+    protected void assertSimpleJSON(String type, Object object, String json) throws Exception {
+        assertSimpleJSON(type, object, json, object);
+    }
+
+    protected void assertSimpleJSON(String type, Object before, String json, Object after) throws Exception {
+        assertEquals("write " + type, json, getJSON(before, false));
+        assertEquals("parse " + type, after, parseJSON(json, false));
+    }
+
+    protected void assertResolveJSON(String type, Object obj, String json) throws Exception {
+        assertEquals("write " + type, json, getJSON(obj, true));
+        assertEquals("parse " + type, obj, parseJSON(json, true));
+    }
+
+    public void testParseBasicTypes() throws Exception {
+        assertSimpleJSON("character", new Character('c'), "\"c\"", "c");
+        assertSimpleJSON("false", Boolean.FALSE, "false");
+        assertSimpleJSON("null", null, "null");
+        assertSimpleJSON("true", Boolean.TRUE, "true");
+        assertSimpleJSON("simple string", "foo", "\"foo\"");
+        assertSimpleJSONByte((byte) 42, "42");
+        assertSimpleJSONFloat(Float.valueOf("1.0625"), "1.0625");
+        assertSimpleJSON(
+            "complex string",
+            "quote(\") backslash(\\) forwardslash(/) backspace(\b) formfeed(\f) newline(\n) carriagereturn(\r) tab(\t) trademark(\u2122)",
+            "\"quote(\\\") backslash(\\\\) forwardslash(\\/) backspace(\\b) formfeed(\\f) newline(\\n) carriagereturn(\\r) tab(\\t) trademark(\\u2122)\""
+        );
+    }
+
+    public void testParseComplexTypes() throws Exception {
+        assertEquals(
+            "parse simple array",
+            list(new Object[] {"foo", new Long(1234), new Double(5.678)}),
+            parseJSON("[, ,\t,\r,\n,\r\n,\"foo\", 1234, 5.678,]", false)
+        );
+        assertSimpleJSON(
+            "simple empty list",
+            list(new Object[] {}),
+            "[]"
+        );
+        assertSimpleJSON(
+            "simple empty array",
+            new Object[] {},
+            "[]",
+            list(new Object[] {})
+        );
+        assertSimpleJSON(
+            "simple array->list",
+            new Object[] {"foo", new Long(1234), new Double(5.678)},
+            "[\n \"foo\",\n 1234,\n 5.678\n]",
+            list(new Object[] {"foo", new Long(1234), new Double(5.678)})
+        );
+        assertSimpleJSON(
+            "simple array",
+            list(new Object[] {"foo", new Long(1234), new Double(5.678)}),
+            "[\n \"foo\",\n 1234,\n 5.678\n]",
+            list(new Object[] {"foo", new Long(1234), new Double(5.678)})
+        );
+        assertEquals(
+            "parse simple map",
+            map(new Object[] {"foo", new Long(1234), "bar", new Double(5.678)}),
+            parseJSON("{, ,\t,\r,\n,\r\n,\"foo\": 1234, \"bar\": 5.678,}", false)
+        );
+        assertSimpleJSON(
+            "parse map",
+            map(new Object[] {"foo", new Long(1234), "bar", new Double(5.678)}),
+            "{\n \"foo\": 1234,\n \"bar\": 5.678\n}"
+        );
+        assertSimpleJSON(
+            "parse empty map",
+            map(new Object[] {}),
+            "{}"
+        );
+        assertEquals(
+            "parse nested map",
+            map(new Object[] {
+                "string",       "this is a string",
+                "integer",      new Long(5000),
+                "double",       new Double(3.1415926),
+                "array",        new Object[] {
+                    "string",
+                    new Long(6000)
+                },
+                "list",         list(new Object[] {
+                    "nested string",
+                    "something",
+                }),
+                "empty-list",   new ArrayList(),
+                "empty-array",  new String[0],
+                "empty-map",    new HashMap(),
+            }),
+            parseJSON("{\"string\": \"this is a string\", \"integer\": 5000, \"double\": 3.1415926, \"array\": [\"string\", 6000], \"list\": [\"nested string\", \"something\"], \"empty-list\": [], \"empty-array\": [], \"empty-map\": {}}", false)
+        );
+    }
+
+    public void testParseErrors() throws Exception {
+        for (char c = 1; c < 1024; c++) {
+            switch (c) {
+                case '[':
+                    doParseExceptionTest("[:", JSONConstants.KEY_SEP);
+                    break;
+                case ']':
+                    doParseExceptionTest("]", JSONConstants.ARRAY_END);
+                    break;
+                case '{':
+                    doParseExceptionTest("{:", JSONConstants.KEY_SEP);
+                    break;
+                case '}':
+                    doParseExceptionTest("}", JSONConstants.OBJECT_END);
+                    break;
+                case ':':
+                    doParseExceptionTest(":", JSONConstants.KEY_SEP);
+                    break;
+                case ',':
+                    doParseExceptionTest(",", JSONConstants.ITEM_SEP);
+                    break;
+                case '"':
+                    doParseExceptionTest("\"", JSONConstants.EOF);
+                    break;
+                case 't':
+                    doParseExceptionTest("true:", JSONConstants.KEY_SEP);
+                    break;
+                case 'f':
+                    doParseExceptionTest("false:", JSONConstants.KEY_SEP);
+                    break;
+                case 'n':
+                    doParseExceptionTest("null:", JSONConstants.KEY_SEP);
+                    break;
+                case '-':   // numbers
+                case '.':
+                case '0': case '1': case '2': case '3': case '4':
+                case '5': case '6': case '7': case '8': case '9':
+                    break;
+                case '\t':
+                    doWhitespaceExceptionTest(Character.toString(c), 8);
+                    break;
+                case '\n':
+                case '\r':
+                case ' ':
+                    doWhitespaceExceptionTest(Character.toString(c), 1);
+                    break;
+                default:
+                    doTokenMgrErrorTest(c);
+                    break;
+            }
+        }
+    }
+
+    protected void doWhitespaceExceptionTest(String s, int column) {
+        ParseException caught = null;
+        try {
+            new JSON(new StringReader(s)).JSONValue();
+        } catch (ParseException e) {
+            caught = e;
+        } finally {
+            assertNotNull("caught exception", caught);
+            assertNotNull("next token(" + s + ")", caught.currentToken);
+            Token next = caught.currentToken.next;
+            assertEquals("next token(" + s + ") is eof", 0, next.kind);
+            assertEquals("begin line(" + s + ")", 1, next.beginLine);
+            assertEquals("begin column(" + s + ")", column, next.beginColumn);
+        }
+    }
+
+    protected void doParseExceptionTest(String s, int nextKind) {
+        ParseException caught = null;
+        try {
+            new JSON(new StringReader(s)).JSONValue();
+        } catch (ParseException e) {
+            caught = e;
+        } finally {
+            assertNotNull("caught exception", caught);
+            assertNotNull("exception message(" + s + ")", caught.getMessage());
+            assertNotNull("next token(" + s + ")", caught.currentToken);
+            Token next = caught.currentToken.next;
+            assertEquals("next token(" + s + ") is correct", nextKind, next.kind);
+            assertEquals("begin line(" + s + ")", 1, next.beginLine);
+            assertEquals("begin column(" + s + ")", s.length(), next.beginColumn);
+        }
+    }
+
+    protected void doTokenMgrErrorTest(char c) throws Exception {
+        TokenMgrError caught = null;
+        try {
+            parseJSON(c + "\"string\"", false);
+        } catch (TokenMgrError e) {
+            caught = e;
+        } finally {
+            assertNotNull("No TokenMgrError thrown for character(" + ((int) c) + ")", caught);
+            // FIXME: maybe extend javacc to return more info in TokenMgrError
+        }
+    }
+
+    public void testResolve() throws Exception {
+        assertResolveJSON("url", new URL("http://ofbiz.apache.org"), "resolve(\"java.net.URL:http:\\/\\/ofbiz.apache.org\")");
+    }
+}