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();
+    }
+
+}