Checking in Jackson 0.9.1 baseline.

diff --git a/src/test/main/BaseTest.java b/src/test/main/BaseTest.java
new file mode 100644
index 0000000..7c4390c
--- /dev/null
+++ b/src/test/main/BaseTest.java
@@ -0,0 +1,109 @@
+package main;
+
+import java.io.*;
+
+import junit.framework.TestCase;
+
+import org.codehaus.jackson.*;
+
+public class BaseTest
+    extends TestCase
+{
+    /*
+    ////////////////////////////////////////////////////////
+    // Some sample documents:
+    ////////////////////////////////////////////////////////
+     */
+
+    protected final static int SAMPLE_SPEC_VALUE_WIDTH = 800;
+    protected final static int SAMPLE_SPEC_VALUE_HEIGHT = 600;
+    protected final static String SAMPLE_SPEC_VALUE_TITLE = "View from 15th Floor";
+    protected final static String SAMPLE_SPEC_VALUE_TN_URL = "http://www.example.com/image/481989943";
+    protected final static int SAMPLE_SPEC_VALUE_TN_HEIGHT = 125;
+    protected final static String SAMPLE_SPEC_VALUE_TN_WIDTH = "100";
+    protected final static int SAMPLE_SPEC_VALUE_TN_ID1 = 116;
+    protected final static int SAMPLE_SPEC_VALUE_TN_ID2 = 943;
+    protected final static int SAMPLE_SPEC_VALUE_TN_ID3 = 234;
+    protected final static int SAMPLE_SPEC_VALUE_TN_ID4 = 38793;
+
+    protected final static String SAMPLE_DOC_JSON_SPEC = 
+        "{\n"
+        +"  \"Image\" : {\n"
+        +"    \"Width\" : "+SAMPLE_SPEC_VALUE_WIDTH+",\n"
+        +"    \"Height\" : "+SAMPLE_SPEC_VALUE_HEIGHT+","
+        +"\"Title\" : \""+SAMPLE_SPEC_VALUE_TITLE+"\",\n"
+        +"    \"Thumbnail\" : {\n"
+        +"      \"Url\" : \""+SAMPLE_SPEC_VALUE_TN_URL+"\",\n"
+        +"\"Height\" : "+SAMPLE_SPEC_VALUE_TN_HEIGHT+",\n"
+        +"      \"Width\" : \""+SAMPLE_SPEC_VALUE_TN_WIDTH+"\"\n"
+        +"    },\n"
+        +"    \"IDs\" : ["+SAMPLE_SPEC_VALUE_TN_ID1+","+SAMPLE_SPEC_VALUE_TN_ID2+","+SAMPLE_SPEC_VALUE_TN_ID3+","+SAMPLE_SPEC_VALUE_TN_ID4+"]\n"
+        +"  }"
+        +"}"
+        ;
+
+    /*
+    ////////////////////////////////////////////////////////
+    // Parser/generator construction
+    ////////////////////////////////////////////////////////
+     */
+
+    protected JsonParser createParserUsingReader(String input)
+        throws IOException, JsonParseException
+    {
+        return new JsonFactory().createJsonParser(new StringReader(input));
+    }
+
+    protected JsonParser createParserUsingStream(String input, String encoding)
+        throws IOException, JsonParseException
+    {
+        byte[] data = input.getBytes(encoding);
+        InputStreamReader is = new InputStreamReader(new ByteArrayInputStream(data), encoding);
+        return new JsonFactory().createJsonParser(is);
+    }
+
+    /*
+    ////////////////////////////////////////////////////////
+    // Additional assertion methods
+    ////////////////////////////////////////////////////////
+     */
+
+    protected void assertToken(JsonToken expToken, JsonToken actToken)
+    {
+        if (actToken != expToken) {
+            fail("Expected token "+expToken+", current token "+actToken);
+        }
+    }
+
+    protected void assertToken(JsonToken expToken, JsonParser jp)
+    {
+        assertToken(expToken, jp.getCurrentToken());
+    }
+
+    protected void verifyException(Exception e, String match)
+    {
+        String msg = e.getMessage();
+        if (msg.indexOf(match) < 0) {
+            fail("Expected an exception with sub-string \""+match+"\": got one with message \""+msg+"\"");
+        }
+    }
+
+    /**
+     * Method that gets textual contents of the current token using
+     * available methods, and ensures results are consistent, before
+     * returning them
+     */
+    protected String getAndVerifyText(JsonParser jp)
+        throws IOException, JsonParseException
+    {
+        String str = jp.getText();
+
+        // Ok, let's verify other accessors
+        int actLen = jp.getTextLength();
+        assertEquals(str.length(), actLen);
+        char[] ch = jp.getTextCharacters();
+        /*String str2 =*/ new String(ch, jp.getTextOffset(), actLen);
+
+        return str;
+    }
+}
diff --git a/src/test/main/TestArrayParsing.java b/src/test/main/TestArrayParsing.java
new file mode 100644
index 0000000..36f6726
--- /dev/null
+++ b/src/test/main/TestArrayParsing.java
@@ -0,0 +1,73 @@
+package main;
+
+import org.codehaus.jackson.*;
+
+/**
+ * Set of additional unit for verifying array parsing, specifically
+ * edge cases.
+ */
+public class TestArrayParsing
+    extends BaseTest
+{
+    public void testValidEmpty()
+        throws Exception
+    {
+        final String DOC = "[   \n  ]";
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+        jp.close();
+    }
+
+    public void testInvalidEmptyMissingClose()
+        throws Exception
+    {
+        final String DOC = "[ ";
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+        try {
+            jp.nextToken();
+            fail("Expected a parsing error for missing array close marker");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "expected close marker for ARRAY");
+        }
+    }
+
+    public void testInvalidMissingFieldName()
+        throws Exception
+    {
+        final String DOC = "[  : 3 ] ";
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+        try {
+            jp.nextToken();
+            fail("Expected a parsing error for missing array close marker");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "Unexpected character");
+        }
+    }
+
+    public void testInvalidExtraComma()
+        throws Exception
+    {
+        final String DOC = "[ 24, ] ";
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(24, jp.getIntValue());
+
+        try {
+            jp.nextToken();
+            fail("Expected a parsing error for missing array close marker");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "expected a value");
+        }
+    }
+}
diff --git a/src/test/main/TestCharEscaping.java b/src/test/main/TestCharEscaping.java
new file mode 100644
index 0000000..95f0cab
--- /dev/null
+++ b/src/test/main/TestCharEscaping.java
@@ -0,0 +1,100 @@
+package main;
+
+import org.codehaus.jackson.*;
+
+/**
+ * Set of basic unit tests for verifying that the basic parser
+ * functionality works as expected.
+ */
+public class TestCharEscaping
+    extends BaseTest
+{
+    public void testMissingEscaping()
+        throws Exception
+    {
+        // Invalid: control chars, including lf, must be escaped
+        final String DOC = "["
+            +"\"Linefeed: \n.\""
+            +"]";
+        JsonParser jp = createParserUsingReader(DOC);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        try {
+            // This may or may not trigger exception
+            JsonToken t = jp.nextToken();
+            assertToken(JsonToken.VALUE_STRING, t);
+            // and if not, should get it here:
+            jp.getText();
+            fail("Expected an exception for un-escaped linefeed in string value");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "has to be escaped");
+        }
+    }
+
+    public void testSimpleEscaping()
+        throws Exception
+    {
+        String DOC = "["
+            +"\"LF=\\n\""
+            +"]";
+
+        JsonParser jp = createParserUsingReader(DOC);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("LF=\n", jp.getText());
+        jp.close();
+
+
+        /* Note: must split Strings, so that javac won't try to handle
+         * escape and inline null char
+         */
+        DOC = "[\"NULL:\\u0000!\"]";
+
+        jp = createParserUsingReader(DOC);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("NULL:\0!", jp.getText());
+
+        // Then just a single char escaping
+        jp = createParserUsingReader("[\"\\u0123\"]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("\u0123", jp.getText());
+
+        // And then double sequence
+        jp = createParserUsingReader("[\"\\u0041\\u0043\"]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("AC", jp.getText());
+    }
+
+    public void testInvalid()
+        throws Exception
+    {
+        // 2-char sequences not allowed:
+        String DOC = "[\"\\u41=A\"]";
+        JsonParser jp = createParserUsingReader(DOC);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        try {
+            jp.nextToken();
+            jp.getText();
+            fail("Expected an exception for unclosed ARRAY");
+        } catch (JsonParseException jpe) {
+            verifyException(jpe, "for character escape");
+        }
+    }
+
+    /**
+     * Test to verify that decoder does not allow 8-digit escapes
+     * (non-BMP characters must be escaped using two 4-digit sequences)
+     */
+    public void test8DigitSequence()
+        throws Exception
+    {
+        String DOC = "[\"\\u00411234\"]";
+        JsonParser jp = createParserUsingReader(DOC);
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("A1234", jp.getText());
+    }
+}
+
diff --git a/src/test/main/TestJsonGenerator.java b/src/test/main/TestJsonGenerator.java
new file mode 100644
index 0000000..5866005
--- /dev/null
+++ b/src/test/main/TestJsonGenerator.java
@@ -0,0 +1,325 @@
+package main;
+
+import org.codehaus.jackson.*;
+
+import java.io.*;
+
+/**
+ * Set of basic unit tests for verifying that the basic generator
+ * functionality works as expected.
+ */
+public class TestJsonGenerator
+    extends BaseTest
+{
+    // // // First, tests for primitive (non-structured) values
+
+    public void testStringWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+        String VALUE = "";
+        gen.writeString(VALUE);
+        gen.close();
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        JsonToken t = jp.nextToken();
+        assertNotNull("Document \""+docStr+"\" yielded no tokens", t);
+        assertEquals(JsonToken.VALUE_STRING, t);
+        assertEquals(VALUE, jp.getText());
+        assertEquals(null, jp.nextToken());
+        jp.close();
+    }
+
+    public void testIntWrite()
+        throws Exception
+    {
+        doTestIntWrite(false);
+        doTestIntWrite(true);
+    }
+
+    public void testLongWrite()
+        throws Exception
+    {
+        doTestLongWrite(false);
+        doTestLongWrite(true);
+    }
+
+    public void testBooleanWrite()
+        throws Exception
+    {
+        for (int i = 0; i < 4; ++i) {
+            boolean state = (i & 1) == 0;
+            boolean pad = (i & 2) == 0;
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+            gen.writeBoolean(state);
+            if (pad) {
+                gen.writeRaw(" ");
+            }
+            gen.close();
+            String docStr = sw.toString();
+            JsonParser jp = createParserUsingReader(docStr);
+            JsonToken t = jp.nextToken();
+            String exp = Boolean.valueOf(state).toString();
+            if (!exp.equals(jp.getText())) {
+                fail("Expected '"+exp+"', got '"+jp.getText());
+            }
+            assertEquals(state ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE, t);
+            assertEquals(null, jp.nextToken());
+            jp.close();
+        }
+    }
+
+    public void testNullWrite()
+        throws Exception
+    {
+        for (int i = 0; i < 2; ++i) {
+            boolean pad = (i & 1) == 0;
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+            gen.writeNull();
+            if (pad) {
+                gen.writeRaw(" ");
+            }
+            gen.close();
+            String docStr = sw.toString();
+            JsonParser jp = createParserUsingReader(docStr);
+            JsonToken t = jp.nextToken();
+            String exp = "null";
+            if (!exp.equals(jp.getText())) {
+                fail("Expected '"+exp+"', got '"+jp.getText());
+            }
+            assertEquals(JsonToken.VALUE_NULL, t);
+            assertEquals(null, jp.nextToken());
+            jp.close();
+        }
+    }
+
+    // // // Then tests for structured values
+
+    public void testEmptyArrayWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+        gen.writeStartArray();
+        gen.writeEndArray();
+        gen.close();
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        jp.close();
+
+        // Ok, then array with nested empty array
+        sw = new StringWriter();
+        gen = new JsonFactory().createJsonGenerator(sw);
+        gen.writeStartArray();
+        gen.writeStartArray();
+        gen.writeEndArray();
+        gen.writeEndArray();
+        gen.close();
+        docStr = sw.toString();
+        jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+        jp.close();
+    }
+
+    public void testInvalidArrayWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+        gen.writeStartArray();
+        // Mismatch:
+        try {
+            gen.writeEndObject();
+            fail("Expected an exception for mismatched array/object write");
+        } catch (JsonGenerationException e) {
+            verifyException(e, "Current context not an object");
+        }
+    }
+
+    public void testSimpleArrayWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+        gen.writeStartArray();
+        gen.writeNumber(13);
+        gen.writeBoolean(true);
+        gen.writeString("foobar");
+        gen.writeEndArray();
+        gen.close();
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(13, jp.getIntValue());
+        assertEquals(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("foobar", jp.getText());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+        jp.close();
+    }
+
+    public void testEmptyObjectWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+        gen.writeStartObject();
+        gen.writeEndObject();
+        gen.close();
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+    }
+
+    public void testInvalidObjectWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+        gen.writeStartObject();
+        // Mismatch:
+        try {
+            gen.writeEndArray();
+            fail("Expected an exception for mismatched array/object write");
+        } catch (JsonGenerationException e) {
+            verifyException(e, "Current context not an array");
+        }
+    }
+
+    public void testSimpleObjectWrite()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+        gen.writeStartObject();
+        gen.writeFieldName("first");
+        gen.writeNumber(-901);
+        gen.writeFieldName("sec");
+        gen.writeBoolean(false);
+        gen.writeFieldName("3rd!"); // json field names are just strings, not ids with restrictions
+        gen.writeString("yee-haw");
+        gen.writeEndObject();
+        gen.close();
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("first", jp.getText());
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(-901, jp.getIntValue());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("sec", jp.getText());
+        assertEquals(JsonToken.VALUE_FALSE, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("3rd!", jp.getText());
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("yee-haw", jp.getText());
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+        jp.close();
+    }
+
+    // // Then root-level output testing
+
+     public void testRootIntsWrite()
+         throws Exception
+     {
+         StringWriter sw = new StringWriter();
+         JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+         gen.writeNumber(1);
+         gen.writeNumber(2);
+         gen.writeNumber(-13);
+         gen.close();
+
+         String docStr = sw.toString();
+
+         JsonParser jp = createParserUsingReader(docStr);
+         assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+         assertEquals(1, jp.getIntValue());
+         assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+         assertEquals(2, jp.getIntValue());
+         assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+         assertEquals(-13, jp.getIntValue());
+         jp.close();
+     }
+
+    /*
+    //////////////////////////////////////////////////
+    // Internal methods
+    //////////////////////////////////////////////////
+     */
+    
+    private void doTestIntWrite(boolean pad)
+        throws Exception
+    {
+        int[] VALUES = new int[] {
+            0, 1, -9, 32, -32, 57, 13240, -9999, Integer.MAX_VALUE, Integer.MAX_VALUE
+        };
+        for (int i = 0; i < VALUES.length; ++i) {
+            int VALUE = VALUES[i];
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+            gen.writeNumber(VALUE);
+            if (pad) {
+                gen.writeRaw(" ");
+            }
+            gen.close();
+            String docStr = sw.toString();
+            JsonParser jp = createParserUsingReader(docStr);
+            JsonToken t = jp.nextToken();
+            assertNotNull("Document \""+docStr+"\" yielded no tokens", t);
+            // Number are always available as lexical representation too
+            String exp = ""+VALUE;
+            if (!exp.equals(jp.getText())) {
+                fail("Expected '"+exp+"', got '"+jp.getText());
+            }
+            assertEquals(JsonToken.VALUE_NUMBER_INT, t);
+            assertEquals(VALUE, jp.getIntValue());
+            assertEquals(null, jp.nextToken());
+            jp.close();
+        }
+    }
+
+    private void doTestLongWrite(boolean pad)
+        throws Exception
+    {
+        long[] VALUES = new long[] {
+            0L, 1L, -1L, -12005002294L, Long.MIN_VALUE, Long.MAX_VALUE
+        };
+        for (int i = 0; i < VALUES.length; ++i) {
+            long VALUE = VALUES[i];
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+            gen.writeNumber(VALUE);
+            if (pad) {
+                gen.writeRaw(" ");
+            }
+            gen.close();
+            String docStr = sw.toString();
+            JsonParser jp = createParserUsingReader(docStr);
+            JsonToken t = jp.nextToken();
+            assertNotNull("Document \""+docStr+"\" yielded no tokens", t);
+            String exp = ""+VALUE;
+            if (!exp.equals(jp.getText())) {
+                fail("Expected '"+exp+"', got '"+jp.getText());
+            }
+            assertEquals(JsonToken.VALUE_NUMBER_INT, t);
+            assertEquals(VALUE, jp.getLongValue());
+            assertEquals(null, jp.nextToken());
+            jp.close();
+        }
+    }
+}
diff --git a/src/test/main/TestJsonParser.java b/src/test/main/TestJsonParser.java
new file mode 100644
index 0000000..de36df2
--- /dev/null
+++ b/src/test/main/TestJsonParser.java
@@ -0,0 +1,262 @@
+package main;
+
+import org.codehaus.jackson.*;
+
+import java.io.IOException;
+
+/**
+ * Set of basic unit tests for verifying that the basic parser
+ * functionality works as expected.
+ */
+public class TestJsonParser
+    extends BaseTest
+{
+
+    /**
+     * This basic unit test verifies that example given in the Json
+     * specification (RFC-4627 or later) is properly parsed at
+     * high-level, without verifying values.
+     */
+    public void testSpecExampleSkipping()
+        throws Exception
+    {
+        doTestSpec(false);
+    }
+
+    /**
+     * Unit test that verifies that the spec example JSON is completely
+     * parsed, and proper values are given for contents of all
+     * events/tokens.
+     */
+    public void testSpecExampleFully()
+        throws Exception
+    {
+        doTestSpec(true);
+    }
+
+    /**
+     * Unit test that verifies that 3 basic keywords (null, true, false)
+     * are properly parsed in various contexts.
+     */
+    public void testKeywords()
+        throws Exception
+    {
+        final String DOC = "{\n"
+            +"\"key1\" : null,\n"
+            +"\"key2\" : true,\n"
+            +"\"key3\" : false,\n"
+            +"\"key4\" : [ false, null, true ]\n"
+            +"}"
+            ;
+
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        verifyFieldName(jp, "key1");
+        assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        verifyFieldName(jp, "key2");
+        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        verifyFieldName(jp, "key3");
+        assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        verifyFieldName(jp, "key4");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+        assertToken(JsonToken.VALUE_NULL, jp.nextToken());
+        assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken());
+    }
+
+    public void testInvalidKeywords()
+        throws Exception
+    {
+        doTestInvalidKeyword1("nul");
+        doTestInvalidKeyword2("nulla", JsonToken.VALUE_NULL);
+        doTestInvalidKeyword1("fal");
+        doTestInvalidKeyword3("False");
+        doTestInvalidKeyword2("falsett0", JsonToken.VALUE_FALSE);
+        doTestInvalidKeyword1("tr");
+        doTestInvalidKeyword1("truE");
+        doTestInvalidKeyword2("trueenough", JsonToken.VALUE_TRUE);
+    }
+
+    /*
+    /////////////////////////////////////////////
+    // Helper methods
+    /////////////////////////////////////////////
+    */
+
+    private void doTestSpec(boolean verify)
+        throws IOException
+    {
+        // First, using a StringReader:
+        doTestSpecIndividual(null, verify);
+
+        // Then with streams using supported encodings:
+        doTestSpecIndividual("UTF-8", verify);
+        doTestSpecIndividual("UTF-16BE", verify);
+        doTestSpecIndividual("UTF-16LE", verify);
+
+        /* Hmmh. UTF-32 is harder only because JDK doesn't come with
+         * a codec for it. Can't test it yet using this method
+         */
+        //doTestSpecIndividual("UTF-32", verify);
+    }
+
+    private void doTestSpecIndividual(String enc, boolean verify)
+        throws IOException
+    {
+        String doc = SAMPLE_DOC_JSON_SPEC;
+        JsonParser jp;
+
+        if (enc == null) {
+            jp = createParserUsingReader(doc);
+        } else {
+            jp = createParserUsingStream(doc, enc);
+        }
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken()); // main object
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Image'
+        if (verify) {
+            verifyFieldName(jp, "Image");
+        }
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken()); // 'image' object
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Width'
+        if (verify) {
+            verifyFieldName(jp, "Width");
+        }
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        if (verify) {
+            verifyIntValue(jp, SAMPLE_SPEC_VALUE_WIDTH);
+        }
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Height'
+        if (verify) {
+            verifyFieldName(jp, "Height");
+        }
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        verifyIntValue(jp, SAMPLE_SPEC_VALUE_HEIGHT);
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Title'
+        if (verify) {
+            verifyFieldName(jp, "Title");
+        }
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals(SAMPLE_SPEC_VALUE_TITLE, getAndVerifyText(jp));
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Thumbnail'
+        if (verify) {
+            verifyFieldName(jp, "Thumbnail");
+        }
+
+        assertToken(JsonToken.START_OBJECT, jp.nextToken()); // 'thumbnail' object
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Url'
+        if (verify) {
+            verifyFieldName(jp, "Url");
+        }
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        if (verify) {
+            assertEquals(SAMPLE_SPEC_VALUE_TN_URL, getAndVerifyText(jp));
+        }
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Height'
+        if (verify) {
+            verifyFieldName(jp, "Height");
+        }
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        verifyIntValue(jp, SAMPLE_SPEC_VALUE_TN_HEIGHT);
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'Width'
+        if (verify) {
+            verifyFieldName(jp, "Width");
+        }
+        // Width value is actually a String in the example
+        assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals(SAMPLE_SPEC_VALUE_TN_WIDTH, getAndVerifyText(jp));
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken()); // 'thumbnail' object
+
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken()); // 'IDs'
+        assertToken(JsonToken.START_ARRAY, jp.nextToken()); // 'ids' array
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken()); // ids[0]
+        verifyIntValue(jp, SAMPLE_SPEC_VALUE_TN_ID1);
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken()); // ids[1]
+        verifyIntValue(jp, SAMPLE_SPEC_VALUE_TN_ID2);
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken()); // ids[2]
+        verifyIntValue(jp, SAMPLE_SPEC_VALUE_TN_ID3);
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken()); // ids[3]
+        verifyIntValue(jp, SAMPLE_SPEC_VALUE_TN_ID4);
+        assertToken(JsonToken.END_ARRAY, jp.nextToken()); // 'ids' array
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken()); // 'image' object
+
+        assertToken(JsonToken.END_OBJECT, jp.nextToken()); // main object
+    }
+
+    private void verifyFieldName(JsonParser jp, String expName)
+        throws IOException
+    {
+        assertEquals(expName, jp.getText());
+        assertEquals(expName, jp.getCurrentName());
+    }
+
+    private void verifyIntValue(JsonParser jp, long expValue)
+        throws IOException
+    {
+        // First, via textual
+        assertEquals(String.valueOf(expValue), jp.getText());
+    }
+
+    private void doTestInvalidKeyword1(String value)
+        throws IOException
+    {
+        JsonParser jp = createParserUsingStream("{ \"key1\" : "+value+" }", "UTF-8");
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        try {
+            jp.nextToken();
+            fail("Expected an exception for malformed value keyword");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "Unrecognized token");
+        }
+    }
+
+    private void doTestInvalidKeyword2(String value, JsonToken firstValue)
+        throws IOException
+    {
+        JsonParser jp = createParserUsingStream("{ \"key1\" : "+value+" }", "UTF-8");
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertToken(firstValue, jp.nextToken());
+        try {
+            jp.nextToken();
+            fail("Expected an exception for malformed value keyword");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "Unexpected character");
+        }
+    }
+
+    private void doTestInvalidKeyword3(String value)
+        throws IOException
+    {
+        JsonParser jp = createParserUsingStream("{ \"key1\" : "+value+" }", "UTF-8");
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        try {
+            jp.nextToken();
+            fail("Expected an exception for malformed value keyword");
+        } catch (JsonParseException jex) {
+            verifyException(jex, "expected a valid value");
+        }
+    }
+}
+
diff --git a/src/test/main/TestNumberParsing.java b/src/test/main/TestNumberParsing.java
new file mode 100644
index 0000000..6d024ec
--- /dev/null
+++ b/src/test/main/TestNumberParsing.java
@@ -0,0 +1,42 @@
+package main;
+
+import org.codehaus.jackson.io.NumberInput;
+
+/**
+ * Set of basic unit tests for verifying that the low-level number
+ * handling methods work as expected.
+ */
+public class TestNumberParsing
+    extends BaseTest
+{
+    public void testIntParsing()
+        throws Exception
+    {
+        char[] testChars = "123456789".toCharArray();
+
+        assertEquals(3, NumberInput.parseInt(testChars, 2, 1));
+        assertEquals(123, NumberInput.parseInt(testChars, 0, 3));
+        assertEquals(2345, NumberInput.parseInt(testChars, 1, 4));
+        assertEquals(9, NumberInput.parseInt(testChars, 8, 1));
+        assertEquals(456789, NumberInput.parseInt(testChars, 3, 6));
+        assertEquals(23456, NumberInput.parseInt(testChars, 1, 5));
+        assertEquals(123456789, NumberInput.parseInt(testChars, 0, 9));
+
+        testChars = "32".toCharArray();
+        assertEquals(32, NumberInput.parseInt(testChars, 0, 2));
+        testChars = "189".toCharArray();
+        assertEquals(189, NumberInput.parseInt(testChars, 0, 3));
+
+        testChars = "10".toCharArray();
+        assertEquals(10, NumberInput.parseInt(testChars, 0, 2));
+        assertEquals(0, NumberInput.parseInt(testChars, 1, 1));
+    }
+
+    public void testLongParsing()
+        throws Exception
+    {
+        char[] testChars = "123456789012345678".toCharArray();
+
+        assertEquals(123456789012345678L, NumberInput.parseLong(testChars, 0, 18));
+    }
+}
diff --git a/src/test/main/TestNumberPrinting.java b/src/test/main/TestNumberPrinting.java
new file mode 100644
index 0000000..04862a6
--- /dev/null
+++ b/src/test/main/TestNumberPrinting.java
@@ -0,0 +1,102 @@
+package main;
+
+import org.codehaus.jackson.io.NumberOutput;
+
+import java.util.Random;
+
+/**
+ * Set of basic unit tests for verifying that the low-level number
+ * printingg methods work as expected.
+ */
+public class TestNumberPrinting
+    extends BaseTest
+{
+    public void testIntPrinting()
+        throws Exception
+    {
+        assertIntPrint(0);
+        assertIntPrint(-3);
+        assertIntPrint(1234);
+        assertIntPrint(-1234);
+        assertIntPrint(56789);
+        assertIntPrint(-56789);
+        assertIntPrint(999999);
+        assertIntPrint(-999999);
+        assertIntPrint(1000000);
+        assertIntPrint(-1000000);
+        assertIntPrint(10000001);
+        assertIntPrint(-10000001);
+        assertIntPrint(-100000012);
+        assertIntPrint(100000012);
+        assertIntPrint(1999888777);
+        assertIntPrint(-1999888777);
+        assertIntPrint(Integer.MAX_VALUE);
+        assertIntPrint(Integer.MIN_VALUE);
+
+        Random rnd = new Random(12345L);
+        for (int i = 0; i < 251000; ++i) {
+            assertIntPrint(rnd.nextInt());
+        }
+    }
+
+    public void testLongPrinting()
+        throws Exception
+    {
+        // First, let's just cover couple of edge cases
+        assertLongPrint(0L, 0);
+        assertLongPrint(1L, 0);
+        assertLongPrint(-1L, 0);
+        assertLongPrint(Long.MAX_VALUE, 0);
+        assertLongPrint(Long.MIN_VALUE, 0);
+        assertLongPrint(Long.MAX_VALUE-1L, 0);
+        assertLongPrint(Long.MIN_VALUE+1L, 0);
+
+        Random rnd = new Random(12345L);
+        // Bigger value space, need more iterations for long
+        for (int i = 0; i < 678000; ++i) {
+            long l = ((long) rnd.nextInt() << 32) | (long) rnd.nextInt();
+            assertLongPrint(l, i);
+        }
+    }
+
+    /*
+    ////////////////////////////////////////////////////////
+    // Internal methods
+    ////////////////////////////////////////////////////////
+     */
+
+    private void assertIntPrint(int value)
+    {
+        String exp = ""+value;
+        String act = printToString(value);
+
+        if (!exp.equals(act)) {
+            assertEquals("Expected conversion (exp '"+exp+"', len "+exp.length()+"; act len "+act.length()+")", exp, act);
+        }
+    }
+
+    private void assertLongPrint(long value, int index)
+    {
+        String exp = ""+value;
+        String act = printToString(value);
+
+        if (!exp.equals(act)) {
+            assertEquals("Expected conversion (exp '"+exp+"', len "+exp.length()+"; act len "+act.length()+"; number index "+index+")", exp, act);
+        }
+    }
+
+    private String printToString(int value)
+    {
+        char[] buffer = new char[12];
+        int offset = NumberOutput.outputInt(value, buffer, 0);
+        return new String(buffer, 0, offset);
+    }
+
+    private String printToString(long value)
+    {
+        char[] buffer = new char[22];
+        int offset = NumberOutput.outputLong(value, buffer, 0);
+        return new String(buffer, 0, offset);
+    }
+}
+
diff --git a/src/test/main/TestNumericValues.java b/src/test/main/TestNumericValues.java
new file mode 100644
index 0000000..529617d
--- /dev/null
+++ b/src/test/main/TestNumericValues.java
@@ -0,0 +1,124 @@
+package main;
+
+import java.math.BigDecimal;
+
+import org.codehaus.jackson.*;
+
+/**
+ * Set of basic unit tests for verifying that the basic parser
+ * functionality works as expected.
+ */
+public class TestNumericValues
+    extends BaseTest
+{
+    public void testSimpleInt()
+        throws Exception
+    {
+        int EXP_I = 1234;
+
+        JsonParser jp = createParserUsingReader("[ "+EXP_I+" ]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(""+EXP_I, jp.getText());
+
+        assertEquals(EXP_I, jp.getIntValue());
+        assertEquals((long) EXP_I, jp.getLongValue());
+        assertEquals((double) EXP_I, jp.getDoubleValue());
+        assertEquals(BigDecimal.valueOf((long) EXP_I), jp.getDecimalValue());
+    }
+
+    public void testSimpleLong()
+        throws Exception
+    {
+        long EXP_L = 12345678907L;
+
+        JsonParser jp = createParserUsingReader("[ "+EXP_L+" ]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(""+EXP_L, jp.getText());
+
+        assertEquals(EXP_L, jp.getLongValue());
+        // Should get an exception if trying to convert to int 
+        try {
+            jp.getIntValue();
+        } catch (JsonParseException jpe) {
+            verifyException(jpe, "out of range");
+        }
+        assertEquals((double) EXP_L, jp.getDoubleValue());
+        assertEquals(BigDecimal.valueOf((long) EXP_L), jp.getDecimalValue());
+    }
+
+    public void testSimpleDouble()
+        throws Exception
+    {
+        /* Testing double is more difficult, given the rounding
+         * errors and such. But let's try anyways.
+         */
+        String EXP_D_STR = "1234.00";
+        double EXP_D = Double.parseDouble(EXP_D_STR);
+
+        JsonParser jp = createParserUsingReader("[ "+EXP_D_STR+" ]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertEquals(EXP_D_STR, jp.getText());
+        assertEquals(EXP_D, jp.getDoubleValue());
+        jp.close();
+
+        EXP_D_STR = "2.1101567E-16";
+        EXP_D = Double.parseDouble(EXP_D_STR);
+
+        jp = createParserUsingReader("[ "+EXP_D_STR+" ]");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertEquals(EXP_D_STR, jp.getText());
+        assertEquals(EXP_D, jp.getDoubleValue());
+        jp.close();
+    }
+
+    public void testNumbers()
+        throws Exception
+    {
+        final String DOC = "[ -13, 8100200300, 13.5, 0.00010, -2.033 ]";
+        JsonParser jp = createParserUsingStream(DOC, "UTF-8");
+
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(-13, jp.getIntValue());
+        assertEquals(-13L, jp.getLongValue());
+        assertEquals(-13., jp.getDoubleValue());
+        assertEquals("-13", jp.getText());
+
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(8100200300L, jp.getLongValue());
+        // Should get exception for overflow:
+        try {
+            /*int x =*/ jp.getIntValue();
+            fail("Expected an exception for overflow");
+        } catch (Exception e) {
+            verifyException(e, "out of range");
+        }
+        assertEquals(8100200300., jp.getDoubleValue());
+        assertEquals("8100200300", jp.getText());
+
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertEquals(13, jp.getIntValue());
+        assertEquals(13L, jp.getLongValue());
+        assertEquals(13.5, jp.getDoubleValue());
+        assertEquals("13.5", jp.getText());
+
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertEquals(0, jp.getIntValue());
+        assertEquals(0L, jp.getLongValue());
+        assertEquals(0.00010, jp.getDoubleValue());
+        assertEquals("0.00010", jp.getText());
+
+        assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertEquals(-2, jp.getIntValue());
+        assertEquals(-2L, jp.getLongValue());
+        assertEquals(-2.033, jp.getDoubleValue());
+        assertEquals("-2.033", jp.getText());
+
+        assertToken(JsonToken.END_ARRAY, jp.nextToken());
+    }
+}
diff --git a/src/test/main/TestPrettyPrinter.java b/src/test/main/TestPrettyPrinter.java
new file mode 100644
index 0000000..afa6deb
--- /dev/null
+++ b/src/test/main/TestPrettyPrinter.java
@@ -0,0 +1,66 @@
+package main;
+
+import org.codehaus.jackson.*;
+
+import java.io.*;
+
+/**
+ * Set of basic unit tests for verifying that indenting
+ * option of generator works correctly
+ */
+public class TestPrettyPrinter
+    extends BaseTest
+{
+    public void testSimpleDoc()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+        gen.useDefaultPrettyPrinter();
+
+        gen.writeStartArray();
+        gen.writeNumber(3);
+        gen.writeString("abc");
+
+        gen.writeStartArray();
+        gen.writeBoolean(true);
+        gen.writeEndArray();
+
+        gen.writeStartObject();
+        gen.writeFieldName("f");
+        gen.writeNull();
+        gen.writeFieldName("f2");
+        gen.writeNull();
+        gen.writeEndObject();
+
+        gen.writeEndArray();
+        gen.close();
+
+        String docStr = sw.toString();
+        JsonParser jp = createParserUsingReader(docStr);
+
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(3, jp.getIntValue());
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("abc", jp.getText());
+
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("f", jp.getText());
+        assertEquals(JsonToken.VALUE_NULL, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("f2", jp.getText());
+        assertEquals(JsonToken.VALUE_NULL, jp.nextToken());
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+
+        jp.close();
+    }
+}
diff --git a/src/test/main/TestScopeMatching.java b/src/test/main/TestScopeMatching.java
new file mode 100644
index 0000000..08eee57
--- /dev/null
+++ b/src/test/main/TestScopeMatching.java
@@ -0,0 +1,73 @@
+package main;
+
+import org.codehaus.jackson.*;
+
+/**
+ * Set of basic unit tests for verifying that Array/Object scopes
+ * are properly matched.
+ */
+public class TestScopeMatching
+    extends BaseTest
+{
+    public void testUnclosedArray()
+        throws Exception
+    {
+        JsonParser jp = createParserUsingReader("[ 1, 2");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+
+        try {
+            jp.nextToken();
+            fail("Expected an exception for unclosed ARRAY");
+        } catch (JsonParseException jpe) {
+            verifyException(jpe, "expected close marker for ARRAY");
+        }
+    }
+
+    public void testUnclosedObject()
+        throws Exception
+    {
+        JsonParser jp = createParserUsingReader("{ \"key\" : 3  ");
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+        assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+
+        try {
+            jp.nextToken();
+            fail("Expected an exception for unclosed OBJECT");
+        } catch (JsonParseException jpe) {
+            verifyException(jpe, "expected close marker for OBJECT");
+        }
+    }
+
+    public void testMismatchArrayToObject()
+        throws Exception
+    {
+        JsonParser jp = createParserUsingReader("[ 1, 2 }");
+        assertToken(JsonToken.START_ARRAY, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+
+        try {
+            jp.nextToken();
+            fail("Expected an exception for incorrectly closed ARRAY");
+        } catch (JsonParseException jpe) {
+            verifyException(jpe, "Unexpected close marker");
+        }
+    }
+
+    public void testMismatchObjectToArray()
+        throws Exception
+    {
+        JsonParser jp = createParserUsingReader("{ ]");
+        assertToken(JsonToken.START_OBJECT, jp.nextToken());
+
+        try {
+            jp.nextToken();
+            fail("Expected an exception for incorrectly closed OBJECT");
+        } catch (JsonParseException jpe) {
+            verifyException(jpe, "Unexpected close marker");
+        }
+    }
+}
diff --git a/src/test/main/TestStringGeneration.java b/src/test/main/TestStringGeneration.java
new file mode 100644
index 0000000..c68b847
--- /dev/null
+++ b/src/test/main/TestStringGeneration.java
@@ -0,0 +1,214 @@
+package main;
+
+import java.io.*;
+
+import org.codehaus.jackson.*;
+
+import java.util.Random;
+
+/**
+ * Set of basic unit tests for verifying that the string
+ * generation, including character escaping, works as expected.
+ */
+public class TestStringGeneration
+    extends BaseTest
+{
+    final static String[] SAMPLES = new String[] {
+        "\"test\"",
+        "\n", "\\n", "\r\n", "a\\b", "tab:\nok?",
+        "a\tb\tc\n\fdef\t \tg\"\"\"h\"\\ijklmn\b",
+        "\"\"\"", "\\r)'\"",
+        "Longer text & other stuff:\twith some\r\n\r\n random linefeeds etc added in to cause some \"special\" handling \\\\ to occur...\n"
+    };
+
+    public void testBasicEscaping()
+        throws Exception
+    {
+        doTestBasicEscaping(false);
+        doTestBasicEscaping(true);
+    }
+
+    public void testLongerRandomSingleChunk()
+        throws Exception
+    {
+        /* Let's first generate 100k of pseudo-random characters, favoring
+         * 7-bit ascii range
+         */
+        for (int round = 0; round < 80; ++round) {
+            String content = generateRandom(75000+round);
+            doTestLongerRandom(content, false);
+            doTestLongerRandom(content, true);
+        }
+    }
+
+    public void testLongerRandomMultiChunk()
+        throws Exception
+    {
+        /* Let's first generate 100k of pseudo-random characters, favoring
+         * 7-bit ascii range
+         */
+        for (int round = 0; round < 70; ++round) {
+            String content = generateRandom(73000+round);
+            doTestLongerRandomMulti(content, false, round);
+            doTestLongerRandomMulti(content, true, round);
+        }
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////////
+    // Internal methods
+    ///////////////////////////////////////////////////////////////
+     */
+
+    private String generateRandom(int len)
+    {
+        StringBuilder sb = new StringBuilder(len+1000); // pad for surrogates
+        Random r = new Random(len);
+        for (int i = 0; i < len; ++i) {
+            if (r.nextBoolean()) { // non-ascii
+                int value = r.nextInt() & 0xFFFF;
+                // Otherwise easy, except that need to ensure that
+                // surrogates are properly paired: and, also
+                // their values do not exceed 0x10FFFF
+                if (value >= 0xD800 && value <= 0xDFFF) {
+                    // Let's discard first value, then, and produce valid pair
+                    int fullValue = (r.nextInt() & 0xFFFFF);
+                    sb.append((char) (0xD800 + (fullValue >> 10)));
+                    value = 0xDC00 + (fullValue & 0x3FF);
+                }
+                sb.append((char) value);
+            } else { // ascii
+                sb.append((char) (r.nextInt() & 0x7F));
+            }
+        }
+        return sb.toString();
+    }   
+
+    private void doTestBasicEscaping(boolean charArray)
+        throws Exception
+    {
+        for (int i = 0; i < SAMPLES.length; ++i) {
+            String VALUE = SAMPLES[i];
+            StringWriter sw = new StringWriter();
+            JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+            gen.writeStartArray();
+            if (charArray) {
+                char[] buf = new char[VALUE.length() + i];
+                VALUE.getChars(0, VALUE.length(), buf, i);
+                gen.writeString(buf, i, VALUE.length());
+            } else {
+                gen.writeString(VALUE);
+            }
+            gen.writeEndArray();
+            gen.close();
+            String docStr = sw.toString();
+            JsonParser jp = createParserUsingReader(docStr);
+            assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+            JsonToken t = jp.nextToken();
+            assertEquals(JsonToken.VALUE_STRING, t);
+            assertEquals(VALUE, jp.getText());
+            assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+            assertEquals(null, jp.nextToken());
+            jp.close();
+        }
+    }
+
+    private void doTestLongerRandom(String text, boolean charArray)
+        throws Exception
+    {
+        ByteArrayOutputStream bow = new ByteArrayOutputStream(text.length());
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(bow, JsonFactory.Encoding.UTF8);
+        gen.writeStartArray();
+        if (charArray) {
+            char[] buf = new char[text.length()];
+            text.getChars(0, text.length(), buf, 0);
+            gen.writeString(buf, 0, text.length());
+        } else {
+            gen.writeString(text);
+        }
+        gen.writeEndArray();
+        gen.close();
+        byte[] docData = bow.toByteArray();
+        JsonParser jp = new JsonFactory().createJsonParser(new ByteArrayInputStream(docData));
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        JsonToken t = jp.nextToken();
+        assertEquals(JsonToken.VALUE_STRING, t);
+        String act = jp.getText();
+        if (!text.equals(act)) {
+            if (text.length() != act.length()) {
+                fail("Expected string length "+text.length()+", actual "+act.length());
+            }
+            int i = 0;
+            for (int len = text.length(); i < len; ++i) {
+                if (text.charAt(i) != act.charAt(i)) {
+                    break;
+                }
+            }
+            fail("Strings differ at position #"+i+" (len "+text.length()+"): expected char 0x"+Integer.toHexString(text.charAt(i))+", actual 0x"+Integer.toHexString(act.charAt(i)));
+        }
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(null, jp.nextToken());
+        jp.close();
+    }
+
+    private void doTestLongerRandomMulti(String text, boolean charArray, int round)
+        throws Exception
+    {
+        ByteArrayOutputStream bow = new ByteArrayOutputStream(text.length());
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(bow, JsonFactory.Encoding.UTF8);
+        gen.writeStartArray();
+
+        gen.writeString(text);
+        gen.writeEndArray();
+        gen.close();
+        
+        gen = new JsonFactory().createJsonGenerator(bow, JsonFactory.Encoding.UTF8);
+        gen.writeStartArray();
+        gen.writeStartArray();
+
+        Random rnd = new Random(text.length());
+        int offset = 0;
+
+        while (offset < text.length()) {
+            int shift = 1 + ((rnd.nextInt() & 0xFFFFF) % 12); // 1 - 12
+            int len = (1 << shift) + shift; // up to 4k
+            if ((offset + len) >= text.length()) {
+                len = text.length() - offset;
+            } else {
+            	// Need to avoid splitting surrogates though
+            	char c = text.charAt(offset+len-1);
+            	if (c >= 0xD800 && c < 0xDC00) {
+            		++len;
+            	}
+            }
+            if (charArray) {
+                char[] buf = new char[len];
+                text.getChars(offset, offset+len, buf, 0);
+                gen.writeString(buf, 0, len);
+            } else {
+                gen.writeString(text.substring(offset, offset+len));
+            }
+            offset += len;
+        }
+
+        gen.writeEndArray();
+        gen.close();
+        byte[] docData = bow.toByteArray();
+        JsonParser jp = new JsonFactory().createJsonParser(new ByteArrayInputStream(docData));
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+
+        offset = 0;
+        while (jp.nextToken() == JsonToken.VALUE_STRING) {
+        	// Let's verify, piece by piece
+        	String act = jp.getText();
+        	String exp = text.substring(offset, offset+act.length());
+        	if (!act.equals(exp)) {
+        		fail("String segment ["+offset+" - "+(offset+act.length())+"[ different");
+        	}
+        	offset += act.length();
+        }
+        assertEquals(JsonToken.END_ARRAY, jp.getCurrentToken());
+        jp.close();
+    }
+
+}
diff --git a/src/test/map/TestFromJavaType.java b/src/test/map/TestFromJavaType.java
new file mode 100644
index 0000000..ddb85b3
--- /dev/null
+++ b/src/test/map/TestFromJavaType.java
@@ -0,0 +1,102 @@
+package map;
+
+import main.BaseTest;
+
+import java.io.*;
+import java.util.*;
+
+import org.codehaus.jackson.*;
+import org.codehaus.jackson.map.*;
+
+/**
+ * This unit test suite tries to verify that the "Native" java type
+ * mapper can properly serialize Java core objects to JSON.
+ */
+public class TestFromJavaType
+    extends BaseTest
+{
+    public void testFromArray()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+
+        ArrayList<Object> doc = new ArrayList<Object>();
+        doc.add("Elem1");
+        doc.add(Integer.valueOf(3));
+        Map<String,Object> struct = new LinkedHashMap<String, Object>();
+        struct.put("first", Boolean.TRUE);
+        struct.put("Second", new ArrayList<Object>());
+        doc.add(struct);
+        doc.add(Boolean.FALSE);
+
+        new JavaTypeMapper().writeAny(gen, doc);
+        gen.close();
+
+        JsonParser jp = new JsonFactory().createJsonParser(new StringReader(sw.toString()));
+
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("Elem1", getAndVerifyText(jp));
+
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(3, jp.getIntValue());
+
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("first", getAndVerifyText(jp));
+
+        assertEquals(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("Second", getAndVerifyText(jp));
+
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+
+        assertEquals(JsonToken.VALUE_FALSE, jp.nextToken());
+
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+    }
+
+    public void testFromMap()
+        throws Exception
+    {
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+
+        LinkedHashMap<String,Object> doc = new LinkedHashMap<String,Object>();
+
+        doc.put("a1", "\"text\"");
+        doc.put("int", Integer.valueOf(137));
+        doc.put("foo bar", Long.valueOf(1234567890L));
+
+        new JavaTypeMapper().writeAny(gen, doc);
+        gen.close();
+
+        JsonParser jp = new JsonFactory().createJsonParser(new StringReader(sw.toString()));
+
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("a1", getAndVerifyText(jp));
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals("\"text\"", getAndVerifyText(jp));
+
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("int", getAndVerifyText(jp));
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(137, jp.getIntValue());
+
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals("foo bar", getAndVerifyText(jp));
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(1234567890L, jp.getLongValue());
+
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+
+        assertNull(jp.nextToken());
+    }
+}
diff --git a/src/test/map/TestFromJsonType.java b/src/test/map/TestFromJsonType.java
new file mode 100644
index 0000000..096dcfb
--- /dev/null
+++ b/src/test/map/TestFromJsonType.java
@@ -0,0 +1,145 @@
+package map;
+
+import main.BaseTest;
+
+import java.io.*;
+
+import org.codehaus.jackson.*;
+import org.codehaus.jackson.map.*;
+
+/**
+ * This unit test suite tries to verify that the "JSON type"
+ * mapper constructed JsonNodes can be serialized properly.
+ */
+public class TestFromJsonType
+    extends BaseTest
+{
+    final static String FIELD1 = "first";
+    final static String FIELD2 = "Second?";
+    final static String FIELD3 = "foo'n \"bar\"";
+    final static String FIELD4 = "4";
+
+    final static String TEXT1 = "Some text & \"stuff\"";
+    final static String TEXT2 = "Some more text:\twith\nlinefeeds and all!";
+
+    final static double DOUBLE_VALUE = 9.25;
+
+    public void testFromArray()
+        throws Exception
+    {
+        JsonTypeMapper mapper = new JsonTypeMapper();
+
+        JsonNode root = mapper.arrayNode();
+        root.appendElement(mapper.textNode(TEXT1));
+        root.appendElement(mapper.numberNode(3));
+        JsonNode obj = mapper.objectNode();
+        root.appendElement(obj);
+        obj.setElement(FIELD1, mapper.booleanNode(true));
+        obj.setElement(FIELD2, mapper.arrayNode());
+        root.appendElement(mapper.booleanNode(false));
+
+        /* Ok, ready... let's serialize using one of two alternate
+         * methods: first preferred (using generator)
+         */
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+        root.writeTo(gen);
+        gen.close();
+        verifyFromArray(sw.toString());
+
+        // And then convenient but less efficient alternative:
+        verifyFromArray(root.toString());
+    }
+
+    public void testFromMap()
+        throws Exception
+    {
+        JsonTypeMapper mapper = new JsonTypeMapper();
+
+        JsonNode root = mapper.objectNode();
+        root.setElement(FIELD4, mapper.textNode(TEXT2));
+        root.setElement(FIELD3, mapper.numberNode(-1));
+        root.setElement(FIELD2, mapper.arrayNode());
+        root.setElement(FIELD1, mapper.numberNode(DOUBLE_VALUE));
+
+        /* Let's serialize using one of two alternate methods:
+         * first preferred (using generator)
+         */
+        StringWriter sw = new StringWriter();
+        JsonGenerator gen = new JsonFactory().createJsonGenerator(sw);
+        root.writeTo(gen);
+        gen.close();
+        verifyFromMap(sw.toString());
+
+        // And then convenient but less efficient alternative:
+        verifyFromMap(root.toString());
+    }
+
+    /*
+    ///////////////////////////////////////////////////////////////
+    // Internal methods
+    ///////////////////////////////////////////////////////////////
+     */
+
+    private void verifyFromArray(String input)
+        throws Exception
+    {
+        JsonParser jp = new JsonFactory().createJsonParser(new StringReader(input));
+        
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals(TEXT1, getAndVerifyText(jp));
+        
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(3, jp.getIntValue());
+        
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(FIELD1, getAndVerifyText(jp));
+        
+        assertEquals(JsonToken.VALUE_TRUE, jp.nextToken());
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(FIELD2, getAndVerifyText(jp));
+        
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+        
+        assertEquals(JsonToken.VALUE_FALSE, jp.nextToken());
+        
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        assertNull(jp.nextToken());
+    }
+
+    private void verifyFromMap(String input)
+        throws Exception
+    {
+        JsonParser jp = new JsonFactory().createJsonParser(new StringReader(input));
+        assertEquals(JsonToken.START_OBJECT, jp.nextToken());
+        
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(FIELD4, getAndVerifyText(jp));
+        assertEquals(JsonToken.VALUE_STRING, jp.nextToken());
+        assertEquals(TEXT2, getAndVerifyText(jp));
+        
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(FIELD3, getAndVerifyText(jp));
+        assertEquals(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+        assertEquals(-1, jp.getIntValue());
+        
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(FIELD2, getAndVerifyText(jp));
+        assertEquals(JsonToken.START_ARRAY, jp.nextToken());
+        assertEquals(JsonToken.END_ARRAY, jp.nextToken());
+        
+        assertEquals(JsonToken.FIELD_NAME, jp.nextToken());
+        assertEquals(FIELD1, getAndVerifyText(jp));
+        assertEquals(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+        assertEquals(DOUBLE_VALUE, jp.getDoubleValue());
+        
+        assertEquals(JsonToken.END_OBJECT, jp.nextToken());
+        
+        assertNull(jp.nextToken());
+    }
+}
diff --git a/src/test/map/TestToJavaType.java b/src/test/map/TestToJavaType.java
new file mode 100644
index 0000000..6180742
--- /dev/null
+++ b/src/test/map/TestToJavaType.java
@@ -0,0 +1,141 @@
+package map;
+
+import main.BaseTest;
+
+import java.io.*;
+
+import org.codehaus.jackson.*;
+import org.codehaus.jackson.map.*;
+import org.codehaus.jackson.map.impl.*;
+
+/**
+ * This unit test suite tries to verify that the "JSON" type
+ * mapper can properly parse JSON and bind contents into appropriate
+ * JsonNode instances.
+ */
+public class TestToJavaType
+    extends BaseTest
+{
+    public void testSimple()
+        throws Exception
+    {
+        final String JSON = SAMPLE_DOC_JSON_SPEC;
+
+        JsonFactory jf = new JsonFactory();
+        JsonNode result = new JsonTypeMapper().read(jf.createJsonParser(new StringReader(JSON)));
+        assertType(result, ObjectNode.class);
+        assertEquals(1, result.size());
+        assertTrue(result.isObject());
+
+        ObjectNode main = (ObjectNode) result;
+        assertEquals("Image", main.getFieldNames().next());
+        JsonNode ob = main.getFieldValues().next();
+        assertType(ob, ObjectNode.class);
+        ObjectNode imageMap = (ObjectNode) ob;
+
+        assertEquals(5, imageMap.size());
+        ob = imageMap.getFieldValue("Width");
+        assertTrue(ob.isIntegralNumber());
+        assertEquals(SAMPLE_SPEC_VALUE_WIDTH, ob.getIntValue());
+
+        ob = imageMap.getFieldValue("Height");
+        assertTrue(ob.isIntegralNumber());
+        assertEquals(SAMPLE_SPEC_VALUE_HEIGHT, ob.getIntValue());
+
+        ob = imageMap.getFieldValue("Title");
+        assertTrue(ob.isTextual());
+        assertEquals(SAMPLE_SPEC_VALUE_TITLE, ob.getTextValue());
+
+        ob = imageMap.getFieldValue("Thumbnail");
+        assertType(ob, ObjectNode.class);
+        ObjectNode tn = (ObjectNode) ob;
+        ob = tn.getFieldValue("Url");
+        assertTrue(ob.isTextual());
+        assertEquals(SAMPLE_SPEC_VALUE_TN_URL, ob.getTextValue());
+        ob = tn.getFieldValue("Height");
+        assertTrue(ob.isIntegralNumber());
+        assertEquals(SAMPLE_SPEC_VALUE_TN_HEIGHT, ob.getIntValue());
+        ob = tn.getFieldValue("Width");
+        assertTrue(ob.isTextual());
+        assertEquals(SAMPLE_SPEC_VALUE_TN_WIDTH, ob.getTextValue());
+
+        ob = imageMap.getFieldValue("IDs");
+        assertTrue(ob.isArray());
+        ArrayNode idList = (ArrayNode) ob;
+        assertEquals(4, idList.size());
+        {
+            int[] values = new int[] {
+                SAMPLE_SPEC_VALUE_TN_ID1,
+                SAMPLE_SPEC_VALUE_TN_ID2,
+                SAMPLE_SPEC_VALUE_TN_ID3,
+                SAMPLE_SPEC_VALUE_TN_ID4
+            };
+            for (int i = 0; i < values.length; ++i) {
+                assertEquals(values[i], idList.getElementValue(i).getIntValue());
+            }
+        }
+    }
+
+    /**
+     * Type mappers should be able to gracefully deal with end of
+     * input.
+     */
+    public void testEOF()
+        throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        String JSON =
+            "{ \"key\": [ { \"a\" : { \"name\": \"foo\",  \"type\": 1\n"
+            +"},  \"type\": 3, \"url\": \"http://www.google.com\" } ],\n"
+            +"\"name\": \"xyz\", \"type\": 1, \"url\" : null }\n  "
+            ;
+        JsonParser jp = jf.createJsonParser(new StringReader(JSON));
+        JsonTypeMapper mapper = new JsonTypeMapper();
+        JsonNode result = mapper.read(jp);
+        assertTrue(result.isObject());
+        assertEquals(4, result.size());
+
+        assertNull(mapper.read(jp));
+    }
+
+    public void testMultipl()
+        throws Exception
+    {
+        JsonFactory jf = new JsonFactory();
+        String JSON = "12  \"string\" [ 1, 2, 3 ]";
+        JsonParser jp = jf.createJsonParser(new StringReader(JSON));
+        JsonTypeMapper mapper = new JsonTypeMapper();
+
+        JsonNode result = mapper.read(jp);
+        assertTrue(result.isIntegralNumber());
+        assertEquals(12, result.getIntValue());
+
+        result = mapper.read(jp);
+        assertTrue(result.isTextual());
+        assertEquals("string", result.getTextValue());
+
+        result = mapper.read(jp);
+        assertTrue(result.isArray());
+        assertEquals(3, result.size());
+
+        assertNull(mapper.read(jp));
+    }
+
+    /*
+    //////////////////////////////////////////////
+    // Helper methods
+    //////////////////////////////////////////////
+     */
+
+    private void assertType(Object ob, Class<?> expType)
+    {
+        if (ob == null) {
+            fail("Expected an object of type "+expType.getName()+", got null");
+        }
+        Class<?> cls = ob.getClass();
+        if (!expType.isAssignableFrom(cls)) {
+            fail("Expected type "+expType.getName()+", got "+cls.getName());
+        }
+    }
+}
+