package org.json.junit;

/*
Copyright (c) 2020 JSON.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;

import org.json.CDL;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONPointerException;
import org.json.JSONTokener;
import org.json.XML;
import org.json.junit.data.BrokenToString;
import org.json.junit.data.ExceptionalBean;
import org.json.junit.data.Fraction;
import org.json.junit.data.GenericBean;
import org.json.junit.data.GenericBeanInt;
import org.json.junit.data.MyBean;
import org.json.junit.data.MyBeanCustomName;
import org.json.junit.data.MyBeanCustomNameSubClass;
import org.json.junit.data.MyBigNumberBean;
import org.json.junit.data.MyEnum;
import org.json.junit.data.MyEnumField;
import org.json.junit.data.MyJsonString;
import org.json.junit.data.MyNumber;
import org.json.junit.data.MyNumberContainer;
import org.json.junit.data.MyPublicClass;
import org.json.junit.data.Singleton;
import org.json.junit.data.SingletonEnum;
import org.json.junit.data.WeirdList;
import org.junit.Test;

import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;

/**
 * JSONObject, along with JSONArray, are the central classes of the reference app.
 * All of the other classes interact with them, and JSON functionality would
 * otherwise be impossible.
 */
public class JSONObjectTest {

    /**
     *  Regular Expression Pattern that matches JSON Numbers. This is primarily used for
     *  output to guarantee that we are always writing valid JSON. 
     */
    static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?");

    /**
     * Tests that the similar method is working as expected.
     */
    @Test
    public void verifySimilar() {
        final String string1 = "HasSameRef";
        final String string2 = "HasDifferentRef";
        JSONObject obj1 = new JSONObject()
                .put("key1", "abc")
                .put("key2", 2)
                .put("key3", string1);
        
        JSONObject obj2 = new JSONObject()
                .put("key1", "abc")
                .put("key2", 3)
                .put("key3", string1);

        JSONObject obj3 = new JSONObject()
                .put("key1", "abc")
                .put("key2", 2)
                .put("key3", new String(string1));
        
        JSONObject obj4 = new JSONObject()
                .put("key1", "abc")
                .put("key2", 2.0)
                .put("key3", new String(string1));

        JSONObject obj5 = new JSONObject()
                .put("key1", "abc")
                .put("key2", 2.0)
                .put("key3", new String(string2));
        
        assertFalse("obj1-obj2 Should eval to false", obj1.similar(obj2));
        assertTrue("obj1-obj3 Should eval to true", obj1.similar(obj3));
        assertTrue("obj1-obj4 Should eval to true", obj1.similar(obj4));
        assertFalse("obj1-obj5 Should eval to false", obj1.similar(obj5));
        // verify that a double and big decimal are "similar"
        assertTrue("should eval to true",new JSONObject().put("a",1.1d).similar(new JSONObject("{\"a\":1.1}")));
        // Confirm #618 is fixed (compare should not exit early if similar numbers are found)
        // Note that this test may not work if the JSONObject map entry order changes
        JSONObject first = new JSONObject("{\"a\": 1, \"b\": 2, \"c\": 3}");
        JSONObject second = new JSONObject("{\"a\": 1, \"b\": 2.0, \"c\": 4}");
        assertFalse("first-second should eval to false", first.similar(second));
    }
    
    @Test
    public void timeNumberParsing() {
        // test data to use
        final String[] testData = new String[] {
                null,
                "",
                "100",
                "-100",
                "abc123",
                "012345",
                "100.5e199",
                "-100.5e199",
                "DEADBEEF",
                "0xDEADBEEF",
                "1234567890.1234567890",
                "-1234567890.1234567890",
                "adloghakuidghauiehgauioehgdkjfb nsruoh aeu noerty384 nkljfgh "
                    + "395h tdfn kdz8yt3 4hkls gn.ey85 4hzfhnz.o8y5a84 onvklt "
                    + "yh389thub nkz8y49lihv al4itlaithknty8hnbl"
                // long (in length) number sequences with invalid data at the end of the
                // string offer very poor performance for the REGEX.
                ,"123467890123467890123467890123467890123467890123467890123467"
                    + "8901234678901234678901234678901234678901234678901234678"
                    + "9012346789012346789012346789012346789012346789012346789"
                    + "0a"
        };
        final int testDataLength = testData.length;
        /**
         * Changed to 1000 for faster test runs
         */
        // final int iterations = 1000000;
        final int iterations = 1000;

        // 10 million iterations 1,000,000 * 10 (currently 100,000)
        long startTime = System.nanoTime();
        for(int i = 0; i < iterations; i++) {
            for(int j = 0; j < testDataLength; j++) {
                try {
                BigDecimal v1 = new BigDecimal(testData[j]);
                v1.signum();
                } catch(Exception ignore) {
                    //do nothing
                }
            }
        }
        final long elapsedNano1 = System.nanoTime() - startTime;
        System.out.println("new BigDecimal(testData[]) : " + elapsedNano1 / 1000000 + " ms");

        startTime = System.nanoTime();
        for(int i = 0; i < iterations; i++) {
            for(int j = 0; j < testDataLength; j++) {
                try {
                boolean v2 = NUMBER_PATTERN.matcher(testData[j]).matches();
                assert v2 == !!v2;
                } catch(Exception ignore) {
                    //do nothing
                }
            }
        }
        final long elapsedNano2 = System.nanoTime() - startTime;
        System.out.println("NUMBER_PATTERN.matcher(testData[]).matches() : " + elapsedNano2 / 1000000 + " ms");
        // don't assert normally as the testing is machine dependent.
        // assertTrue("Expected Pattern matching to be faster than BigDecimal constructor",elapsedNano2<elapsedNano1);
   }

    /**
     * JSONObject built from a bean, but only using a null value.
     * Nothing good is expected to happen.
     * Expects NullPointerException
     */
    @Test(expected=NullPointerException.class)
    public void jsonObjectByNullBean() {
        assertNull("Expected an exception",new JSONObject((MyBean)null));
    }
    
    /**
     * The JSON parser is permissive of unambiguous unquoted keys and values.
     * Such JSON text should be allowed, even if it does not strictly conform
     * to the spec. However, after being parsed, toString() should emit strictly
     * conforming JSON text.  
     */
    @Test
    public void unquotedText() {
        String str = "{key1:value1, key2:42}";
        JSONObject jsonObject = new JSONObject(str);
        String textStr = jsonObject.toString();
        assertTrue("expected key1", textStr.contains("\"key1\""));
        assertTrue("expected value1", textStr.contains("\"value1\""));
        assertTrue("expected key2", textStr.contains("\"key2\""));
        assertTrue("expected 42", textStr.contains("42"));
    }
    
    @Test
    public void testLongFromString(){
        String str = "26315000000253009";
        JSONObject json = new JSONObject();
        json.put("key", str);
        
        final Object actualKey = json.opt("key");
        assert str.equals(actualKey) : "Incorrect key value. Got " + actualKey
                + " expected " + str;
        
        final long actualLong = json.optLong("key");
        assert actualLong != 0 : "Unable to extract long value for string " + str;
        assert 26315000000253009L == actualLong : "Incorrect key value. Got "
                + actualLong + " expected " + str;

        final String actualString = json.optString("key");
        assert str.equals(actualString) : "Incorrect key value. Got "
                + actualString + " expected " + str;
    }
    
    /**
     * A JSONObject can be created with no content
     */
    @Test
    public void emptyJsonObject() {
        JSONObject jsonObject = new JSONObject();
        assertTrue("jsonObject should be empty", jsonObject.isEmpty());
    }

    /**
     * A JSONObject can be created from another JSONObject plus a list of names.
     * In this test, some of the starting JSONObject keys are not in the 
     * names list.
     */
    @Test
    public void jsonObjectByNames() {
        String str = 
            "{"+
                "\"trueKey\":true,"+
                "\"falseKey\":false,"+
                "\"nullKey\":null,"+
                "\"stringKey\":\"hello world!\","+
                "\"escapeStringKey\":\"h\be\tllo w\u1234orld!\","+
                "\"intKey\":42,"+
                "\"doubleKey\":-23.45e67"+
            "}";
        String[] keys = {"falseKey", "stringKey", "nullKey", "doubleKey"};
        JSONObject jsonObject = new JSONObject(str);

        // validate JSON
        JSONObject jsonObjectByName = new JSONObject(jsonObject, keys);
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObjectByName.toString());
        assertTrue("expected 4 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 4);
        assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObjectByName.query("/falseKey")));
        assertTrue("expected \"nullKey\":null", JSONObject.NULL.equals(jsonObjectByName.query("/nullKey")));
        assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObjectByName.query("/stringKey")));
        assertTrue("expected \"doubleKey\":-23.45e67", new BigDecimal("-23.45e67").equals(jsonObjectByName.query("/doubleKey")));
    }

    /**
     * JSONObjects can be built from a Map<String, Object>. 
     * In this test the map is null.
     * the JSONObject(JsonTokener) ctor is not tested directly since it already
     * has full coverage from other tests.
     */
    @Test
    public void jsonObjectByNullMap() {
        Map<String, Object> map = null;
        JSONObject jsonObject = new JSONObject(map);
        assertTrue("jsonObject should be empty", jsonObject.isEmpty());
    }

    /**
     * JSONObjects can be built from a Map<String, Object>. 
     * In this test all of the map entries are valid JSON types.
     */
    @Test
    public void jsonObjectByMap() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("trueKey", new Boolean(true));
        map.put("falseKey", new Boolean(false));
        map.put("stringKey", "hello world!");
        map.put("escapeStringKey", "h\be\tllo w\u1234orld!");
        map.put("intKey", new Long(42));
        map.put("doubleKey", new Double(-23.45e67));
        JSONObject jsonObject = new JSONObject(map);

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 6 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 6);
        assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
        assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
        assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey")));
        assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
        assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
    }

    /**
     * Verifies that the constructor has backwards compatability with RAW types pre-java5.
     */
    @Test
    public void verifyConstructor() {
        
        final JSONObject expected = new JSONObject("{\"myKey\":10}");
        
        @SuppressWarnings("rawtypes")
        Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10));
        JSONObject jaRaw = new JSONObject(myRawC);

        Map<String, Object> myCStrObj = Collections.singletonMap("myKey",
                (Object) Integer.valueOf(10));
        JSONObject jaStrObj = new JSONObject(myCStrObj);

        Map<String, Integer> myCStrInt = Collections.singletonMap("myKey",
                Integer.valueOf(10));
        JSONObject jaStrInt = new JSONObject(myCStrInt);

        Map<?, ?> myCObjObj = Collections.singletonMap((Object) "myKey",
                (Object) Integer.valueOf(10));
        JSONObject jaObjObj = new JSONObject(myCObjObj);

        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaRaw));
        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaStrObj));
        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaStrInt));
        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaObjObj));
    }
    
    /**
     * Tests Number serialization.
     */
    @Test
    public void verifyNumberOutput(){
        /**
         * MyNumberContainer is a POJO, so call JSONObject(bean), 
         * which builds a map of getter names/values
         * The only getter is getMyNumber (key=myNumber), 
         * whose return value is MyNumber. MyNumber extends Number, 
         * but is not recognized as such by wrap() per current
         * implementation, so wrap() returns the default new JSONObject(bean).
         * The only getter is getNumber (key=number), whose return value is
         * BigDecimal(42). 
         */
        JSONObject jsonObject = new JSONObject(new MyNumberContainer());
        String actual = jsonObject.toString();
        String expected = "{\"myNumber\":{\"number\":42}}";
        assertEquals("Equal", expected , actual);
        
        /**
         * JSONObject.put() handles objects differently than the 
         * bean constructor. Where the bean ctor wraps objects before 
         * placing them in the map, put() inserts the object without wrapping.
         * In this case, a MyNumber instance is the value.
         * The MyNumber.toString() method is responsible for
         * returning a reasonable value: the string '42'.
         */
        jsonObject = new JSONObject();
        jsonObject.put("myNumber", new MyNumber());
        actual = jsonObject.toString();
        expected = "{\"myNumber\":42}";
        assertEquals("Equal", expected , actual);

        /**
         * Calls the JSONObject(Map) ctor, which calls wrap() for values.
         * AtomicInteger is a Number, but is not recognized by wrap(), per
         * current implementation. However, the type is
         * 'java.util.concurrent.atomic', so due to the 'java' prefix,
         * wrap() inserts the value as a string. That is why 42 comes back
         * wrapped in quotes.
         */
        jsonObject = new JSONObject(Collections.singletonMap("myNumber", new AtomicInteger(42)));
        actual = jsonObject.toString();
        expected = "{\"myNumber\":\"42\"}";
        assertEquals("Equal", expected , actual);

        /**
         * JSONObject.put() inserts the AtomicInteger directly into the
         * map not calling wrap(). In toString()->write()->writeValue(), 
         * AtomicInteger is recognized as a Number, and converted via
         * numberToString() into the unquoted string '42'.
         */
        jsonObject = new JSONObject();
        jsonObject.put("myNumber", new AtomicInteger(42));
        actual = jsonObject.toString();
        expected = "{\"myNumber\":42}";
        assertEquals("Equal", expected , actual);

        /**
         * Calls the JSONObject(Map) ctor, which calls wrap() for values.
         * Fraction is a Number, but is not recognized by wrap(), per
         * current implementation. As a POJO, Fraction is handled as a
         * bean and inserted into a contained JSONObject. It has 2 getters,
         * for numerator and denominator. 
         */
        jsonObject = new JSONObject(Collections.singletonMap("myNumber", new Fraction(4,2)));
        assertEquals(1, jsonObject.length());
        assertEquals(2, ((JSONObject)(jsonObject.get("myNumber"))).length());
        assertEquals("Numerator", BigInteger.valueOf(4) , jsonObject.query("/myNumber/numerator"));
        assertEquals("Denominator", BigInteger.valueOf(2) , jsonObject.query("/myNumber/denominator"));

        /**
         * JSONObject.put() inserts the Fraction directly into the
         * map not calling wrap(). In toString()->write()->writeValue(), 
         * Fraction is recognized as a Number, and converted via
         * numberToString() into the unquoted string '4/2'. But the 
         * BigDecimal sanity check fails, so writeValue() defaults
         * to returning a safe JSON quoted string. Pretty slick!
         */
        jsonObject = new JSONObject();
        jsonObject.put("myNumber", new Fraction(4,2));
        actual = jsonObject.toString();
        expected = "{\"myNumber\":\"4/2\"}"; // valid JSON, bug fixed
        assertEquals("Equal", expected , actual);
    }

    /**
     * Verifies that the put Collection has backwards compatibility with RAW types pre-java5.
     */
    @Test
    public void verifyPutCollection() {
        
        final JSONObject expected = new JSONObject("{\"myCollection\":[10]}");

        @SuppressWarnings("rawtypes")
        Collection myRawC = Collections.singleton(Integer.valueOf(10));
        JSONObject jaRaw = new JSONObject();
        jaRaw.put("myCollection", myRawC);

        Collection<Object> myCObj = Collections.singleton((Object) Integer
                .valueOf(10));
        JSONObject jaObj = new JSONObject();
        jaObj.put("myCollection", myCObj);

        Collection<Integer> myCInt = Collections.singleton(Integer
                .valueOf(10));
        JSONObject jaInt = new JSONObject();
        jaInt.put("myCollection", myCInt);

        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaRaw));
        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaObj));
        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaInt));
    }

    
    /**
     * Verifies that the put Map has backwards compatibility with RAW types pre-java5.
     */
    @Test
    public void verifyPutMap() {
        
        final JSONObject expected = new JSONObject("{\"myMap\":{\"myKey\":10}}");

        @SuppressWarnings("rawtypes")
        Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10));
        JSONObject jaRaw = new JSONObject();
        jaRaw.put("myMap", myRawC);

        Map<String, Object> myCStrObj = Collections.singletonMap("myKey",
                (Object) Integer.valueOf(10));
        JSONObject jaStrObj = new JSONObject();
        jaStrObj.put("myMap", myCStrObj);

        Map<String, Integer> myCStrInt = Collections.singletonMap("myKey",
                Integer.valueOf(10));
        JSONObject jaStrInt = new JSONObject();
        jaStrInt.put("myMap", myCStrInt);

        Map<?, ?> myCObjObj = Collections.singletonMap((Object) "myKey",
                (Object) Integer.valueOf(10));
        JSONObject jaObjObj = new JSONObject();
        jaObjObj.put("myMap", myCObjObj);

        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaRaw));
        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaStrObj));
        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaStrInt));
        assertTrue(
                "The RAW Collection should give me the same as the Typed Collection",
                expected.similar(jaObjObj));
    }


    /**
     * JSONObjects can be built from a Map<String, Object>. 
     * In this test the map entries are not valid JSON types.
     * The actual conversion is kind of interesting.
     */
    @Test
    public void jsonObjectByMapWithUnsupportedValues() {
        Map<String, Object> jsonMap = new HashMap<String, Object>();
        // Just insert some random objects
        jsonMap.put("key1", new CDL());
        jsonMap.put("key2", new Exception());

        JSONObject jsonObject = new JSONObject(jsonMap);

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 2 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 2);
        assertTrue("expected 0 key1 items", ((Map<?,?>)(JsonPath.read(doc, "$.key1"))).size() == 0);
        assertTrue("expected \"key2\":java.lang.Exception","java.lang.Exception".equals(jsonObject.query("/key2")));
    }

    /**
     * JSONObjects can be built from a Map<String, Object>. 
     * In this test one of the map values is null 
     */
    @Test
    public void jsonObjectByMapWithNullValue() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("trueKey", new Boolean(true));
        map.put("falseKey", new Boolean(false));
        map.put("stringKey", "hello world!");
        map.put("nullKey", null);
        map.put("escapeStringKey", "h\be\tllo w\u1234orld!");
        map.put("intKey", new Long(42));
        map.put("doubleKey", new Double(-23.45e67));
        JSONObject jsonObject = new JSONObject(map);

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 6 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 6);
        assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
        assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
        assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey")));
        assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
        assertTrue("expected \"intKey\":42", Long.valueOf("42").equals(jsonObject.query("/intKey")));
        assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
    }

    /**
     * JSONObject built from a bean. In this case all but one of the 
     * bean getters return valid JSON types
     */
    @SuppressWarnings("boxing")
    @Test
    public void jsonObjectByBean1() {
        /**
         * Default access classes have to be mocked since JSONObject, which is
         * not in the same package, cannot call MyBean methods by reflection.
         */
        MyBean myBean = mock(MyBean.class);
        when(myBean.getDoubleKey()).thenReturn(-23.45e7);
        when(myBean.getIntKey()).thenReturn(42);
        when(myBean.getStringKey()).thenReturn("hello world!");
        when(myBean.getEscapeStringKey()).thenReturn("h\be\tllo w\u1234orld!");
        when(myBean.isTrueKey()).thenReturn(true);
        when(myBean.isFalseKey()).thenReturn(false);
        when(myBean.getStringReaderKey()).thenReturn(
            new StringReader("") {
            });

        JSONObject jsonObject = new JSONObject(myBean);

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 8 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 8);
        assertTrue("expected 0 items in stringReaderKey", ((Map<?, ?>) (JsonPath.read(doc, "$.stringReaderKey"))).size() == 0);
        assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
        assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
        assertTrue("expected hello world!","hello world!".equals(jsonObject.query("/stringKey")));
        assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
        assertTrue("expected 42", Integer.valueOf("42").equals(jsonObject.query("/intKey")));
        assertTrue("expected -23.45e7", Double.valueOf("-23.45e7").equals(jsonObject.query("/doubleKey")));
        // sorry, mockito artifact
        assertTrue("expected 2 callbacks items", ((List<?>)(JsonPath.read(doc, "$.callbacks"))).size() == 2);
        assertTrue("expected 0 handler items", ((Map<?,?>)(JsonPath.read(doc, "$.callbacks[0].handler"))).size() == 0);
        assertTrue("expected 0 callbacks[1] items", ((Map<?,?>)(JsonPath.read(doc, "$.callbacks[1]"))).size() == 0);
    }

    /**
     * JSONObject built from a bean that has custom field names.
     */
    @Test
    public void jsonObjectByBean2() {
        JSONObject jsonObject = new JSONObject(new MyBeanCustomName());
        assertNotNull(jsonObject);
        assertEquals("Wrong number of keys found:",
                5,
                jsonObject.keySet().size());
        assertFalse("Normal field name (someString) processing did not work",
                jsonObject.has("someString"));
        assertFalse("Normal field name (myDouble) processing did not work",
                jsonObject.has("myDouble"));
        assertFalse("Normal field name (someFloat) processing did not work",
                jsonObject.has("someFloat"));
        assertFalse("Ignored field not found!",
                jsonObject.has("ignoredInt"));
        // getSomeInt() has no user-defined annotation
        assertTrue("Normal field name (someInt) should have been found",
                jsonObject.has("someInt"));
        // the user-defined annotation does not replace any value, so someLong should be found
        assertTrue("Normal field name (someLong) should have been found",
                jsonObject.has("someLong"));
        // myStringField replaces someString property name via user-defined annotation
        assertTrue("Overridden String field name (myStringField) should have been found",
                jsonObject.has("myStringField"));
        // weird name replaces myDouble property name via user-defined annotation
        assertTrue("Overridden String field name (Some Weird NAme that Normally Wouldn't be possible!) should have been found",
                jsonObject.has("Some Weird NAme that Normally Wouldn't be possible!"));
        // InterfaceField replaces someFloat property name via user-defined annotation
        assertTrue("Overridden String field name (InterfaceField) should have been found",
                jsonObject.has("InterfaceField"));
    }
    
    /**
     * JSONObject built from a bean that has custom field names inherited from a parent class.
     */
    @Test
    public void jsonObjectByBean3() {
        JSONObject jsonObject = new JSONObject(new MyBeanCustomNameSubClass());
        assertNotNull(jsonObject);
        assertEquals("Wrong number of keys found:",
                7,
                jsonObject.keySet().size());
        assertFalse("Normal int field name (someInt) found, but was overridden",
                jsonObject.has("someInt"));
        assertFalse("Normal field name (myDouble) processing did not work",
                jsonObject.has("myDouble"));
        // myDouble was replaced by weird name, and then replaced again by AMoreNormalName via user-defined annotation
        assertFalse("Overridden String field name (Some Weird NAme that Normally Wouldn't be possible!) should not be FOUND!",
                jsonObject.has("Some Weird NAme that Normally Wouldn't be possible!"));
        assertFalse("Normal field name (someFloat) found, but was overridden",
                jsonObject.has("someFloat"));
        assertFalse("Ignored field found! but was overridden",
                jsonObject.has("ignoredInt"));
        // shouldNotBeJSON property name was first ignored, then replaced by ShouldBeIgnored via user-defined annotations
        assertFalse("Ignored field at the same level as forced name should not have been found",
                jsonObject.has("ShouldBeIgnored"));
        // able property name was replaced by Getable via user-defined annotation
        assertFalse("Normally ignored field (able) with explicit property name should not have been found",
                jsonObject.has("able"));
        // property name someInt was replaced by newIntFieldName via user-defined annotation
        assertTrue("Overridden int field name (newIntFieldName) should have been found",
                jsonObject.has("newIntFieldName"));
        // property name someLong was not replaced via user-defined annotation
        assertTrue("Normal field name (someLong) should have been found",
                jsonObject.has("someLong"));
        // property name someString was replaced by myStringField via user-defined annotation
        assertTrue("Overridden String field name (myStringField) should have been found",
                jsonObject.has("myStringField"));
        // property name myDouble was replaced by a weird name, followed by AMoreNormalName via user-defined annotations
        assertTrue("Overridden double field name (AMoreNormalName) should have been found",
                jsonObject.has("AMoreNormalName"));
        // property name someFloat was replaced by InterfaceField via user-defined annotation
        assertTrue("Overridden String field name (InterfaceField) should have been found",
                jsonObject.has("InterfaceField"));
        // property name ignoredInt was replaced by none, followed by forcedInt via user-defined annotations
        assertTrue("Forced field should have been found!",
                jsonObject.has("forcedInt"));
        // property name able was replaced by Getable via user-defined annotation
        assertTrue("Overridden boolean field name (Getable) should have been found",
                jsonObject.has("Getable"));
    }

    /**
     * A bean is also an object. But in order to test the JSONObject
     * ctor that takes an object and a list of names, 
     * this particular bean needs some public
     * data members, which have been added to the class.
     */
    @Test
    public void jsonObjectByObjectAndNames() {
        String[] keys = {"publicString", "publicInt"};
        // just need a class that has public data members
        MyPublicClass myPublicClass = new MyPublicClass();
        JSONObject jsonObject = new JSONObject(myPublicClass, keys);

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 2 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 2);
        assertTrue("expected \"publicString\":\"abc\"", "abc".equals(jsonObject.query("/publicString")));
        assertTrue("expected \"publicInt\":42", Integer.valueOf(42).equals(jsonObject.query("/publicInt")));
    }

    /**
     * Exercise the JSONObject from resource bundle functionality.
     * The test resource bundle is uncomplicated, but provides adequate test coverage.
     */
    @Test
    public void jsonObjectByResourceBundle() {
        JSONObject jsonObject = new
                JSONObject("org.json.junit.data.StringsResourceBundle",
                        Locale.getDefault());

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 2 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 2);
        assertTrue("expected 2 greetings items", ((Map<?,?>)(JsonPath.read(doc, "$.greetings"))).size() == 2);
        assertTrue("expected \"hello\":\"Hello, \"", "Hello, ".equals(jsonObject.query("/greetings/hello")));
        assertTrue("expected \"world\":\"World!\"", "World!".equals(jsonObject.query("/greetings/world")));
        assertTrue("expected 2 farewells items", ((Map<?,?>)(JsonPath.read(doc, "$.farewells"))).size() == 2);
        assertTrue("expected \"later\":\"Later, \"", "Later, ".equals(jsonObject.query("/farewells/later")));
        assertTrue("expected \"world\":\"World!\"", "Alligator!".equals(jsonObject.query("/farewells/gator")));
    }
    
    /**
     * Exercise the JSONObject.accumulate() method
     */
    @SuppressWarnings("boxing")
    @Test
    public void jsonObjectAccumulate() {

        JSONObject jsonObject = new JSONObject();
        jsonObject.accumulate("myArray", true);
        jsonObject.accumulate("myArray", false);
        jsonObject.accumulate("myArray", "hello world!");
        jsonObject.accumulate("myArray", "h\be\tllo w\u1234orld!");
        jsonObject.accumulate("myArray", 42);
        jsonObject.accumulate("myArray", -23.45e7);
        // include an unsupported object for coverage
        try {
            jsonObject.accumulate("myArray", Double.NaN);
            fail("Expected exception");
        } catch (JSONException ignored) {}

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 1 top level item", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 1);
        assertTrue("expected 6 myArray items", ((List<?>)(JsonPath.read(doc, "$.myArray"))).size() == 6);
        assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0")));
        assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1")));
        assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2")));
        assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3")));
        assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4")));
        assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5")));
    }

    /**
     * Exercise the JSONObject append() functionality
     */
    @SuppressWarnings("boxing")
    @Test
    public void jsonObjectAppend() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.append("myArray", true);
        jsonObject.append("myArray", false);
        jsonObject.append("myArray", "hello world!");
        jsonObject.append("myArray", "h\be\tllo w\u1234orld!");
        jsonObject.append("myArray", 42);
        jsonObject.append("myArray", -23.45e7);
        // include an unsupported object for coverage
        try {
            jsonObject.append("myArray", Double.NaN);
            fail("Expected exception");
        } catch (JSONException ignored) {}

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 1 top level item", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 1);
        assertTrue("expected 6 myArray items", ((List<?>)(JsonPath.read(doc, "$.myArray"))).size() == 6);
        assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0")));
        assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1")));
        assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2")));
        assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3")));
        assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4")));
        assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5")));
    }

    /**
     * Exercise the JSONObject doubleToString() method
     */
    @SuppressWarnings("boxing")
    @Test
    public void jsonObjectDoubleToString() {
        String [] expectedStrs = {"1", "1", "-23.4", "-2.345E68", "null", "null" };
        Double [] doubles = { 1.0, 00001.00000, -23.4, -23.45e67, 
                Double.NaN, Double.NEGATIVE_INFINITY }; 
        for (int i = 0; i < expectedStrs.length; ++i) {
            String actualStr = JSONObject.doubleToString(doubles[i]);
            assertTrue("value expected ["+expectedStrs[i]+
                    "] found ["+actualStr+ "]",
                    expectedStrs[i].equals(actualStr));
        }
    }

    /**
     * Exercise some JSONObject get[type] and opt[type] methods
     */
    @Test
    public void jsonObjectValues() {
        String str = 
            "{"+
                "\"trueKey\":true,"+
                "\"falseKey\":false,"+
                "\"trueStrKey\":\"true\","+
                "\"falseStrKey\":\"false\","+
                "\"stringKey\":\"hello world!\","+
                "\"intKey\":42,"+
                "\"intStrKey\":\"43\","+
                "\"longKey\":1234567890123456789,"+
                "\"longStrKey\":\"987654321098765432\","+
                "\"doubleKey\":-23.45e7,"+
                "\"doubleStrKey\":\"00001.000\","+
                "\"BigDecimalStrKey\":\"19007199254740993.35481234487103587486413587843213584\","+
                "\"negZeroKey\":-0.0,"+
                "\"negZeroStrKey\":\"-0.0\","+
                "\"arrayKey\":[0,1,2],"+
                "\"objectKey\":{\"myKey\":\"myVal\"}"+
            "}";
        JSONObject jsonObject = new JSONObject(str);
        assertTrue("trueKey should be true", jsonObject.getBoolean("trueKey"));
        assertTrue("opt trueKey should be true", jsonObject.optBoolean("trueKey"));
        assertTrue("falseKey should be false", !jsonObject.getBoolean("falseKey"));
        assertTrue("trueStrKey should be true", jsonObject.getBoolean("trueStrKey"));
        assertTrue("trueStrKey should be true", jsonObject.optBoolean("trueStrKey"));
        assertTrue("falseStrKey should be false", !jsonObject.getBoolean("falseStrKey"));
        assertTrue("stringKey should be string",
            jsonObject.getString("stringKey").equals("hello world!"));
        assertTrue("doubleKey should be double", 
                jsonObject.getDouble("doubleKey") == -23.45e7);
        assertTrue("doubleStrKey should be double", 
                jsonObject.getDouble("doubleStrKey") == 1);
        assertTrue("doubleKey can be float", 
                jsonObject.getFloat("doubleKey") == -23.45e7f);
        assertTrue("doubleStrKey can be float", 
                jsonObject.getFloat("doubleStrKey") == 1f);
        assertTrue("opt doubleKey should be double", 
                jsonObject.optDouble("doubleKey") == -23.45e7);
        assertTrue("opt doubleKey with Default should be double", 
                jsonObject.optDouble("doubleStrKey", Double.NaN) == 1);
        assertTrue("opt negZeroKey should be a Double", 
                jsonObject.opt("negZeroKey") instanceof Double);
        assertTrue("get negZeroKey should be a Double", 
                jsonObject.get("negZeroKey") instanceof Double);
        assertTrue("optNumber negZeroKey should return Double",
                jsonObject.optNumber("negZeroKey") instanceof Double);
        assertTrue("optNumber negZeroStrKey should return Double",
                jsonObject.optNumber("negZeroStrKey") instanceof Double);
        assertTrue("opt negZeroKey should be double", 
                Double.compare(jsonObject.optDouble("negZeroKey"), -0.0d) == 0);
        assertTrue("opt negZeroStrKey with Default should be double", 
                Double.compare(jsonObject.optDouble("negZeroStrKey"), -0.0d) == 0);
        assertTrue("optNumber negZeroKey should be -0.0", 
                Double.compare(jsonObject.optNumber("negZeroKey").doubleValue(), -0.0d) == 0);
        assertTrue("optNumber negZeroStrKey should be -0.0", 
                Double.compare(jsonObject.optNumber("negZeroStrKey").doubleValue(), -0.0d) == 0);
        assertTrue("optFloat doubleKey should be float", 
                jsonObject.optFloat("doubleKey") == -23.45e7f);
        assertTrue("optFloat doubleKey with Default should be float", 
                jsonObject.optFloat("doubleStrKey", Float.NaN) == 1f);
        assertTrue("intKey should be int", 
                jsonObject.optInt("intKey") == 42);
        assertTrue("opt intKey should be int", 
                jsonObject.optInt("intKey", 0) == 42);
        assertTrue("opt intKey with default should be int", 
                jsonObject.getInt("intKey") == 42);
        assertTrue("intStrKey should be int", 
                jsonObject.getInt("intStrKey") == 43);
        assertTrue("longKey should be long", 
                jsonObject.getLong("longKey") == 1234567890123456789L);
        assertTrue("opt longKey should be long", 
                jsonObject.optLong("longKey") == 1234567890123456789L);
        assertTrue("opt longKey with default should be long", 
                jsonObject.optLong("longKey", 0) == 1234567890123456789L);
        assertTrue("longStrKey should be long", 
                jsonObject.getLong("longStrKey") == 987654321098765432L);
        assertTrue("optNumber int should return Integer",
                jsonObject.optNumber("intKey") instanceof Integer);
        assertTrue("optNumber long should return Long",
                jsonObject.optNumber("longKey") instanceof Long);
        assertTrue("optNumber double should return BigDecimal",
                jsonObject.optNumber("doubleKey") instanceof BigDecimal);
        assertTrue("optNumber Str int should return Integer",
                jsonObject.optNumber("intStrKey") instanceof Integer);
        assertTrue("optNumber Str long should return Long",
                jsonObject.optNumber("longStrKey") instanceof Long);
        assertTrue("optNumber Str double should return BigDecimal",
                jsonObject.optNumber("doubleStrKey") instanceof BigDecimal);
        assertTrue("optNumber BigDecimalStrKey should return BigDecimal",
                jsonObject.optNumber("BigDecimalStrKey") instanceof BigDecimal);
        assertTrue("xKey should not exist",
                jsonObject.isNull("xKey"));
        assertTrue("stringKey should exist",
                jsonObject.has("stringKey"));
        assertTrue("opt stringKey should string",
                jsonObject.optString("stringKey").equals("hello world!"));
        assertTrue("opt stringKey with default should string",
                jsonObject.optString("stringKey", "not found").equals("hello world!"));
        JSONArray jsonArray = jsonObject.getJSONArray("arrayKey");
        assertTrue("arrayKey should be JSONArray", 
                jsonArray.getInt(0) == 0 &&
                jsonArray.getInt(1) == 1 &&
                jsonArray.getInt(2) == 2);
        jsonArray = jsonObject.optJSONArray("arrayKey");
        assertTrue("opt arrayKey should be JSONArray", 
                jsonArray.getInt(0) == 0 &&
                jsonArray.getInt(1) == 1 &&
                jsonArray.getInt(2) == 2);
        JSONObject jsonObjectInner = jsonObject.getJSONObject("objectKey");
        assertTrue("objectKey should be JSONObject", 
                jsonObjectInner.get("myKey").equals("myVal"));
    }

    /**
     * Check whether JSONObject handles large or high precision numbers correctly
     */
    @Test
    public void stringToValueNumbersTest() {
        assertTrue("-0 Should be a Double!",JSONObject.stringToValue("-0")  instanceof Double);
        assertTrue("-0.0 Should be a Double!",JSONObject.stringToValue("-0.0") instanceof Double);
        assertTrue("'-' Should be a String!",JSONObject.stringToValue("-") instanceof String);
        assertTrue( "0.2 should be a BigDecimal!",
                JSONObject.stringToValue( "0.2" ) instanceof BigDecimal );
        assertTrue( "Doubles should be BigDecimal, even when incorrectly converting floats!",
                JSONObject.stringToValue( new Double( "0.2f" ).toString() ) instanceof BigDecimal );
        /**
         * This test documents a need for BigDecimal conversion.
         */
        Object obj = JSONObject.stringToValue( "299792.457999999984" );
        assertTrue( "does not evaluate to 299792.457999999984 BigDecimal!",
                 obj.equals(new BigDecimal("299792.457999999984")) );
        assertTrue( "1 should be an Integer!",
                JSONObject.stringToValue( "1" ) instanceof Integer );
        assertTrue( "Integer.MAX_VALUE should still be an Integer!",
                JSONObject.stringToValue( new Integer( Integer.MAX_VALUE ).toString() ) instanceof Integer );
        assertTrue( "Large integers should be a Long!",
                JSONObject.stringToValue( Long.valueOf(((long)Integer.MAX_VALUE) + 1 ) .toString() ) instanceof Long );
        assertTrue( "Long.MAX_VALUE should still be an Integer!",
                JSONObject.stringToValue( new Long( Long.MAX_VALUE ).toString() ) instanceof Long );

        String str = new BigInteger( new Long( Long.MAX_VALUE ).toString() ).add( BigInteger.ONE ).toString();
        assertTrue( "Really large integers currently evaluate to BigInteger",
                JSONObject.stringToValue(str).equals(new BigInteger("9223372036854775808")));
    }

    /**
     * This test documents numeric values which could be numerically
     * handled as BigDecimal or BigInteger. It helps determine what outputs
     * will change if those types are supported.
     */
    @Test
    public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() {
        // Valid JSON Numbers, probably should return BigDecimal or BigInteger objects
        String str = 
            "{"+
                "\"numberWithDecimals\":299792.457999999984,"+
                "\"largeNumber\":12345678901234567890,"+
                "\"preciseNumber\":0.2000000000000000111,"+
                "\"largeExponent\":-23.45e2327"+
            "}";
        JSONObject jsonObject = new JSONObject(str);
        // Comes back as a double, but loses precision
        assertTrue( "numberWithDecimals currently evaluates to double 299792.458",
                jsonObject.get( "numberWithDecimals" ).equals( new BigDecimal( "299792.457999999984" ) ) );
        Object obj = jsonObject.get( "largeNumber" );
        assertTrue("largeNumber currently evaluates to BigInteger",
                new BigInteger("12345678901234567890").equals(obj));
        // comes back as a double but loses precision
        assertEquals( "preciseNumber currently evaluates to double 0.2",
                0.2, jsonObject.getDouble( "preciseNumber" ), 0.0);
        obj = jsonObject.get( "largeExponent" );
        assertTrue("largeExponent should evaluate as a BigDecimal",
                new BigDecimal("-23.45e2327").equals(obj));
    }

    /**
     * This test documents how JSON-Java handles invalid numeric input.
     */
    @Test
    public void jsonInvalidNumberValues() {
            // Number-notations supported by Java and invalid as JSON
        String str = 
            "{"+
                "\"hexNumber\":-0x123,"+
                "\"tooManyZeros\":00,"+
                "\"negativeInfinite\":-Infinity,"+
                "\"negativeNaN\":-NaN,"+
                "\"negativeFraction\":-.01,"+
                "\"tooManyZerosFraction\":00.001,"+
                "\"negativeHexFloat\":-0x1.fffp1,"+
                "\"hexFloat\":0x1.0P-1074,"+
                "\"floatIdentifier\":0.1f,"+
                "\"doubleIdentifier\":0.1d"+
            "}";
        JSONObject jsonObject = new JSONObject(str);
        Object obj;
        obj = jsonObject.get( "hexNumber" );
        assertFalse( "hexNumber must not be a number (should throw exception!?)",
                 obj instanceof Number );
        assertTrue("hexNumber currently evaluates to string",
                obj.equals("-0x123"));
        assertTrue( "tooManyZeros currently evaluates to string",
                jsonObject.get( "tooManyZeros" ).equals("00"));
        obj = jsonObject.get("negativeInfinite");
        assertTrue( "negativeInfinite currently evaluates to string",
                obj.equals("-Infinity"));
        obj = jsonObject.get("negativeNaN");
        assertTrue( "negativeNaN currently evaluates to string",
                obj.equals("-NaN"));
        assertTrue( "negativeFraction currently evaluates to double -0.01",
                jsonObject.get( "negativeFraction" ).equals(BigDecimal.valueOf(-0.01)));
        assertTrue( "tooManyZerosFraction currently evaluates to double 0.001",
                jsonObject.get( "tooManyZerosFraction" ).equals(BigDecimal.valueOf(0.001)));
        assertTrue( "negativeHexFloat currently evaluates to double -3.99951171875",
                jsonObject.get( "negativeHexFloat" ).equals(Double.valueOf(-3.99951171875)));
        assertTrue("hexFloat currently evaluates to double 4.9E-324",
                jsonObject.get("hexFloat").equals(Double.valueOf(4.9E-324)));
        assertTrue("floatIdentifier currently evaluates to double 0.1",
                jsonObject.get("floatIdentifier").equals(Double.valueOf(0.1)));
        assertTrue("doubleIdentifier currently evaluates to double 0.1",
                jsonObject.get("doubleIdentifier").equals(Double.valueOf(0.1)));
    }

    /**
     * Tests how JSONObject get[type] handles incorrect types
     */
    @Test
    public void jsonObjectNonAndWrongValues() {
        String str = 
            "{"+
                "\"trueKey\":true,"+
                "\"falseKey\":false,"+
                "\"trueStrKey\":\"true\","+
                "\"falseStrKey\":\"false\","+
                "\"stringKey\":\"hello world!\","+
                "\"intKey\":42,"+
                "\"intStrKey\":\"43\","+
                "\"longKey\":1234567890123456789,"+
                "\"longStrKey\":\"987654321098765432\","+
                "\"doubleKey\":-23.45e7,"+
                "\"doubleStrKey\":\"00001.000\","+
                "\"arrayKey\":[0,1,2],"+
                "\"objectKey\":{\"myKey\":\"myVal\"}"+
            "}";
        JSONObject jsonObject = new JSONObject(str);
        try {
            jsonObject.getBoolean("nonKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("expecting an exception message", 
                    "JSONObject[\"nonKey\"] not found.", e.getMessage());
        }
        try {
            jsonObject.getBoolean("stringKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "JSONObject[\"stringKey\"] is not a Boolean.",
                    e.getMessage());
        }
        try {
            jsonObject.getString("nonKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "JSONObject[\"nonKey\"] not found.",
                    e.getMessage());
        }
        try {
            jsonObject.getString("trueKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "JSONObject[\"trueKey\"] is not a string.",
                    e.getMessage());
        }
        try {
            jsonObject.getDouble("nonKey");
            fail("Expected an exception");
        } catch (JSONException e) {
            assertEquals("Expecting an exception message",
                    "JSONObject[\"nonKey\"] not found.",
                    e.getMessage());
        }
        try {
            jsonObject.getDouble("stringKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message",
                    "JSONObject[\"stringKey\"] is not a double.",
                    e.getMessage());
        }
        try {
            jsonObject.getFloat("nonKey");
            fail("Expected an exception");
        } catch (JSONException e) {
            assertEquals("Expecting an exception message",
                    "JSONObject[\"nonKey\"] not found.",
                    e.getMessage());
        }
        try {
            jsonObject.getFloat("stringKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message",
                    "JSONObject[\"stringKey\"] is not a float.",
                    e.getMessage());
        }
        try {
            jsonObject.getInt("nonKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message",
                    "JSONObject[\"nonKey\"] not found.",
                    e.getMessage());
        }
        try {
            jsonObject.getInt("stringKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "JSONObject[\"stringKey\"] is not a int.",
                    e.getMessage());
        }
        try {
            jsonObject.getLong("nonKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "JSONObject[\"nonKey\"] not found.",
                    e.getMessage());
        }
        try {
            jsonObject.getLong("stringKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "JSONObject[\"stringKey\"] is not a long.",
                    e.getMessage());
        }
        try {
            jsonObject.getJSONArray("nonKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "JSONObject[\"nonKey\"] not found.",
                    e.getMessage());
        }
        try {
            jsonObject.getJSONArray("stringKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "JSONObject[\"stringKey\"] is not a JSONArray.",
                    e.getMessage());
        }
        try {
            jsonObject.getJSONObject("nonKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "JSONObject[\"nonKey\"] not found.",
                    e.getMessage());
        }
        try {
            jsonObject.getJSONObject("stringKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "JSONObject[\"stringKey\"] is not a JSONObject.",
                    e.getMessage());
        }
    }

    /**
     * This test documents an unexpected numeric behavior.
     * A double that ends with .0 is parsed, serialized, then
     * parsed again. On the second parse, it has become an int.
     */
    @Test
    public void unexpectedDoubleToIntConversion() {
        String key30 = "key30";
        String key31 = "key31";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put(key30, new Double(3.0));
        jsonObject.put(key31, new Double(3.1));

        assertTrue("3.0 should remain a double",
                jsonObject.getDouble(key30) == 3); 
        assertTrue("3.1 should remain a double",
                jsonObject.getDouble(key31) == 3.1); 
 
        // turns 3.0 into 3.
        String serializedString = jsonObject.toString();
        JSONObject deserialized = new JSONObject(serializedString);
        assertTrue("3.0 is now an int", deserialized.get(key30) instanceof Integer);
        assertTrue("3.0 can still be interpreted as a double",
                deserialized.getDouble(key30) == 3.0);
        assertTrue("3.1 remains a double", deserialized.getDouble(key31) == 3.1);
    }

    /**
     * Document behaviors of big numbers. Includes both JSONObject
     * and JSONArray tests
     */
    @SuppressWarnings("boxing")
    @Test
    public void bigNumberOperations() {
        /**
         * JSONObject tries to parse BigInteger as a bean, but it only has
         * one getter, getLowestBitSet(). The value is lost and an unhelpful
         * value is stored. This should be fixed.
         */
        BigInteger bigInteger = new BigInteger("123456789012345678901234567890");
        JSONObject jsonObject = new JSONObject(bigInteger);
        Object obj = jsonObject.get("lowestSetBit");
        assertTrue("JSONObject only has 1 value", jsonObject.length() == 1);
        assertTrue("JSONObject parses BigInteger as the Integer lowestBitSet",
                obj instanceof Integer);
        assertTrue("this bigInteger lowestBitSet happens to be 1",
                obj.equals(1));

        /**
         * JSONObject tries to parse BigDecimal as a bean, but it has
         * no getters, The value is lost and no value is stored.
         * This should be fixed.
         */
        BigDecimal bigDecimal = new BigDecimal(
                "123456789012345678901234567890.12345678901234567890123456789");
        jsonObject = new JSONObject(bigDecimal);
        assertTrue("large bigDecimal is not stored", jsonObject.isEmpty());

        /**
         * JSONObject put(String, Object) method stores and serializes
         * bigInt and bigDec correctly. Nothing needs to change. 
         */
        jsonObject = new JSONObject();
        jsonObject.put("bigInt", bigInteger);
        assertTrue("jsonObject.put() handles bigInt correctly",
                jsonObject.get("bigInt").equals(bigInteger));
        assertTrue("jsonObject.getBigInteger() handles bigInt correctly",
                jsonObject.getBigInteger("bigInt").equals(bigInteger));
        assertTrue("jsonObject.optBigInteger() handles bigInt correctly",
                jsonObject.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger));
        assertTrue("jsonObject serializes bigInt correctly",
                jsonObject.toString().equals("{\"bigInt\":123456789012345678901234567890}"));
        assertTrue("BigInteger as BigDecimal",
                jsonObject.getBigDecimal("bigInt").equals(new BigDecimal(bigInteger)));

        
        jsonObject = new JSONObject();
        jsonObject.put("bigDec", bigDecimal);
        assertTrue("jsonObject.put() handles bigDec correctly",
                jsonObject.get("bigDec").equals(bigDecimal));
        assertTrue("jsonObject.getBigDecimal() handles bigDec correctly",
                jsonObject.getBigDecimal("bigDec").equals(bigDecimal));
        assertTrue("jsonObject.optBigDecimal() handles bigDec correctly",
                jsonObject.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal));
        assertTrue("jsonObject serializes bigDec correctly",
                jsonObject.toString().equals(
                "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));

        assertTrue("BigDecimal as BigInteger",
                jsonObject.getBigInteger("bigDec").equals(bigDecimal.toBigInteger()));
        /**
         * exercise some exceptions
         */
        try {
            // bigInt key does not exist
            jsonObject.getBigDecimal("bigInt");
            fail("expected an exeption");
        } catch (JSONException ignored) {}
        obj = jsonObject.optBigDecimal("bigInt", BigDecimal.ONE);
        assertTrue("expected BigDecimal", obj.equals(BigDecimal.ONE));
        jsonObject.put("stringKey",  "abc");
        try {
            jsonObject.getBigDecimal("stringKey");
            fail("expected an exeption");
        } catch (JSONException ignored) {}
        obj = jsonObject.optBigInteger("bigDec", BigInteger.ONE);
        assertTrue("expected BigInteger", obj instanceof BigInteger);
        assertEquals(bigDecimal.toBigInteger(), obj);

        /**
         * JSONObject.numberToString() works correctly, nothing to change.
         */
        String str = JSONObject.numberToString(bigInteger);
        assertTrue("numberToString() handles bigInteger correctly",
                str.equals("123456789012345678901234567890"));
        str = JSONObject.numberToString(bigDecimal);
        assertTrue("numberToString() handles bigDecimal correctly",
                str.equals("123456789012345678901234567890.12345678901234567890123456789"));

        /**
         * JSONObject.stringToValue() turns bigInt into an accurate string,
         * and rounds bigDec. This incorrect, but users may have come to 
         * expect this behavior. Change would be marginally better, but 
         * might inconvenience users.
         */
        obj = JSONObject.stringToValue(bigInteger.toString());
        assertTrue("stringToValue() turns bigInteger string into Number",
                obj instanceof Number);
        obj = JSONObject.stringToValue(bigDecimal.toString());
        assertTrue("stringToValue() changes bigDecimal Number",
                obj instanceof Number);

        /**
         * wrap() vs put() big number behavior is now the same.
         */
        // bigInt map ctor 
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("bigInt", bigInteger);
        jsonObject = new JSONObject(map);
        String actualFromMapStr = jsonObject.toString();
        assertTrue("bigInt in map (or array or bean) is a string",
                actualFromMapStr.equals(
                "{\"bigInt\":123456789012345678901234567890}"));
        // bigInt put
        jsonObject = new JSONObject();
        jsonObject.put("bigInt", bigInteger);
        String actualFromPutStr = jsonObject.toString();
        assertTrue("bigInt from put is a number",
                actualFromPutStr.equals(
                "{\"bigInt\":123456789012345678901234567890}"));
        // bigDec map ctor
        map = new HashMap<String, Object>();
        map.put("bigDec", bigDecimal);
        jsonObject = new JSONObject(map);
        actualFromMapStr = jsonObject.toString();
        assertTrue("bigDec in map (or array or bean) is a bigDec",
                actualFromMapStr.equals(
                "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
        // bigDec put
        jsonObject = new JSONObject();
        jsonObject.put("bigDec", bigDecimal);
        actualFromPutStr = jsonObject.toString();
        assertTrue("bigDec from put is a number",
                actualFromPutStr.equals(
                "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
        // bigInt,bigDec put 
        JSONArray jsonArray = new JSONArray();
        jsonArray.put(bigInteger);
        jsonArray.put(bigDecimal);
        actualFromPutStr = jsonArray.toString();
        assertTrue("bigInt, bigDec from put is a number",
                actualFromPutStr.equals(
                "[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]"));
        assertTrue("getBigInt is bigInt", jsonArray.getBigInteger(0).equals(bigInteger));
        assertTrue("getBigDec is bigDec", jsonArray.getBigDecimal(1).equals(bigDecimal));
        assertTrue("optBigInt is bigInt", jsonArray.optBigInteger(0, BigInteger.ONE).equals(bigInteger));
        assertTrue("optBigDec is bigDec", jsonArray.optBigDecimal(1, BigDecimal.ONE).equals(bigDecimal));
        jsonArray.put(Boolean.TRUE);
        try {
            jsonArray.getBigInteger(2);
            fail("should not be able to get big int");
        } catch (Exception ignored) {}
        try {
            jsonArray.getBigDecimal(2);
            fail("should not be able to get big dec");
        } catch (Exception ignored) {}
        assertTrue("optBigInt is default", jsonArray.optBigInteger(2, BigInteger.ONE).equals(BigInteger.ONE));
        assertTrue("optBigDec is default", jsonArray.optBigDecimal(2, BigDecimal.ONE).equals(BigDecimal.ONE));

        // bigInt,bigDec list ctor
        List<Object> list = new ArrayList<Object>();
        list.add(bigInteger);
        list.add(bigDecimal);
        jsonArray = new JSONArray(list);
        String actualFromListStr = jsonArray.toString();
        assertTrue("bigInt, bigDec in list is a bigInt, bigDec",
                actualFromListStr.equals(
                "[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]"));
        // bigInt bean ctor
        MyBigNumberBean myBigNumberBean = mock(MyBigNumberBean.class);
        when(myBigNumberBean.getBigInteger()).thenReturn(new BigInteger("123456789012345678901234567890"));
        jsonObject = new JSONObject(myBigNumberBean);
        String actualFromBeanStr = jsonObject.toString();
        // can't do a full string compare because mockery adds an extra key/value
        assertTrue("bigInt from bean ctor is a bigInt",
                actualFromBeanStr.contains("123456789012345678901234567890"));
        // bigDec bean ctor
        myBigNumberBean = mock(MyBigNumberBean.class);
        when(myBigNumberBean.getBigDecimal()).thenReturn(new BigDecimal("123456789012345678901234567890.12345678901234567890123456789"));
        jsonObject = new JSONObject(myBigNumberBean);
        actualFromBeanStr = jsonObject.toString();
        // can't do a full string compare because mockery adds an extra key/value
        assertTrue("bigDec from bean ctor is a bigDec",
                actualFromBeanStr.contains("123456789012345678901234567890.12345678901234567890123456789"));
        // bigInt,bigDec wrap()
        obj = JSONObject.wrap(bigInteger);
        assertTrue("wrap() returns big num",obj.equals(bigInteger));
        obj = JSONObject.wrap(bigDecimal);
        assertTrue("wrap() returns string",obj.equals(bigDecimal));

    }

    /**
     * The purpose for the static method getNames() methods are not clear.
     * This method is not called from within JSON-Java. Most likely
     * uses are to prep names arrays for:  
     * JSONObject(JSONObject jo, String[] names)
     * JSONObject(Object object, String names[]),
     */
    @Test
    public void jsonObjectNames() {
        JSONObject jsonObject;

        // getNames() from null JSONObject
        assertTrue("null names from null Object", 
                null == JSONObject.getNames((Object)null));

        // getNames() from object with no fields
        assertTrue("null names from Object with no fields", 
                null == JSONObject.getNames(new MyJsonString()));

        // getNames from new JSONOjbect
        jsonObject = new JSONObject();
        String [] names = JSONObject.getNames(jsonObject);
        assertTrue("names should be null", names == null);

        
        // getNames() from empty JSONObject
        String emptyStr = "{}";
        jsonObject = new JSONObject(emptyStr);
        assertTrue("empty JSONObject should have null names",
                null == JSONObject.getNames(jsonObject));

        // getNames() from JSONObject
        String str = 
            "{"+
                "\"trueKey\":true,"+
                "\"falseKey\":false,"+
                "\"stringKey\":\"hello world!\","+
            "}";
        jsonObject = new JSONObject(str);
        names = JSONObject.getNames(jsonObject);
        JSONArray jsonArray = new JSONArray(names);

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider()
                .parse(jsonArray.toString());
        List<?> docList = JsonPath.read(doc, "$");
        assertTrue("expected 3 items", docList.size() == 3);
        assertTrue(
                "expected to find trueKey",
                ((List<?>) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1);
        assertTrue(
                "expected to find falseKey",
                ((List<?>) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1);
        assertTrue(
                "expected to find stringKey",
                ((List<?>) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1);

        /**
         * getNames() from an enum with properties has an interesting result.
         * It returns the enum values, not the selected enum properties
         */
        MyEnumField myEnumField = MyEnumField.VAL1;
        names = JSONObject.getNames(myEnumField);

        // validate JSON
        jsonArray = new JSONArray(names);
        doc = Configuration.defaultConfiguration().jsonProvider()
                .parse(jsonArray.toString());
        docList = JsonPath.read(doc, "$");
        assertTrue("expected 3 items", docList.size() == 3);
        assertTrue(
                "expected to find VAL1",
                ((List<?>) JsonPath.read(doc, "$[?(@=='VAL1')]")).size() == 1);
        assertTrue(
                "expected to find VAL2",
                ((List<?>) JsonPath.read(doc, "$[?(@=='VAL2')]")).size() == 1);
        assertTrue(
                "expected to find VAL3",
                ((List<?>) JsonPath.read(doc, "$[?(@=='VAL3')]")).size() == 1);

        /**
         * A bean is also an object. But in order to test the static
         * method getNames(), this particular bean needs some public
         * data members.
         */
        MyPublicClass myPublicClass = new MyPublicClass();
        names = JSONObject.getNames(myPublicClass);

        // validate JSON
        jsonArray = new JSONArray(names);
        doc = Configuration.defaultConfiguration().jsonProvider()
                .parse(jsonArray.toString());
        docList = JsonPath.read(doc, "$");
        assertTrue("expected 2 items", docList.size() == 2);
        assertTrue(
                "expected to find publicString",
                ((List<?>) JsonPath.read(doc, "$[?(@=='publicString')]")).size() == 1);
        assertTrue(
                "expected to find publicInt",
                ((List<?>) JsonPath.read(doc, "$[?(@=='publicInt')]")).size() == 1);
    }

    /**
     * Populate a JSONArray from an empty JSONObject names() method.
     * It should be empty.
     */
    @Test
    public void emptyJsonObjectNamesToJsonAray() {
        JSONObject jsonObject = new JSONObject();
        JSONArray jsonArray = jsonObject.names();
        assertTrue("jsonArray should be null", jsonArray == null);
    }

    /**
     * Populate a JSONArray from a JSONObject names() method.
     * Confirm that it contains the expected names.
     */
    @Test
    public void jsonObjectNamesToJsonAray() {
        String str = 
            "{"+
                "\"trueKey\":true,"+
                "\"falseKey\":false,"+
                "\"stringKey\":\"hello world!\","+
            "}";

        JSONObject jsonObject = new JSONObject(str);
        JSONArray jsonArray = jsonObject.names();

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
        assertTrue("expected 3 top level items", ((List<?>)(JsonPath.read(doc, "$"))).size() == 3);
        assertTrue("expected to find trueKey", ((List<?>) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1);
        assertTrue("expected to find falseKey", ((List<?>) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1);
        assertTrue("expected to find stringKey", ((List<?>) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1);
    }

    /**
     * Exercise the JSONObject increment() method.
     */
    @SuppressWarnings("cast")
    @Test
    public void jsonObjectIncrement() {
        String str = 
            "{"+
                "\"keyLong\":9999999991,"+
                "\"keyDouble\":1.1"+
             "}";
        JSONObject jsonObject = new JSONObject(str);
        jsonObject.increment("keyInt");
        jsonObject.increment("keyInt");
        jsonObject.increment("keyLong");
        jsonObject.increment("keyDouble");
        jsonObject.increment("keyInt");
        jsonObject.increment("keyLong");
        jsonObject.increment("keyDouble");
        /**
         * JSONObject constructor won't handle these types correctly, but
         * adding them via put works.
         */
        jsonObject.put("keyFloat", 1.1f);
        jsonObject.put("keyBigInt", new BigInteger("123456789123456789123456789123456780"));
        jsonObject.put("keyBigDec", new BigDecimal("123456789123456789123456789123456780.1"));
        jsonObject.increment("keyFloat");
        jsonObject.increment("keyFloat");
        jsonObject.increment("keyBigInt");
        jsonObject.increment("keyBigDec");

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 6 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 6);
        assertTrue("expected 3", Integer.valueOf(3).equals(jsonObject.query("/keyInt")));
        assertTrue("expected 9999999993", Long.valueOf(9999999993L).equals(jsonObject.query("/keyLong")));
        assertTrue("expected 3.1", BigDecimal.valueOf(3.1).equals(jsonObject.query("/keyDouble")));
        assertTrue("expected 123456789123456789123456789123456781", new BigInteger("123456789123456789123456789123456781").equals(jsonObject.query("/keyBigInt")));
        assertTrue("expected 123456789123456789123456789123456781.1", new BigDecimal("123456789123456789123456789123456781.1").equals(jsonObject.query("/keyBigDec")));

        /**
         * Should work the same way on any platform! @see https://docs.oracle
         * .com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3 This is the
         * effect of a float to double conversion and is inherent to the
         * shortcomings of the IEEE 754 format, when converting 32-bit into
         * double-precision 64-bit. Java type-casts float to double. A 32 bit
         * float is type-casted to 64 bit double by simply appending zero-bits
         * to the mantissa (and extended the signed exponent by 3 bits.) and
         * there is no way to obtain more information than it is stored in the
         * 32-bits float.
         * 
         * Like 1/3 cannot be represented as base10 number because it is
         * periodically, 1/5 (for example) cannot be represented as base2 number
         * since it is periodically in base2 (take a look at
         * http://www.h-schmidt.net/FloatConverter/) The same happens to 3.1,
         * that decimal number (base10 representation) is periodic in base2
         * representation, therefore appending zero-bits is inaccurate. Only
         * repeating the periodically occurring bits (0110) would be a proper
         * conversion. However one cannot detect from a 32 bit IEE754
         * representation which bits would "repeat infinitely", since the
         * missing bits would not fit into the 32 bit float, i.e. the
         * information needed simply is not there!
         */
        assertEquals(Float.valueOf(3.1f), jsonObject.query("/keyFloat"));

        /**
         * float f = 3.1f; double df = (double) f; double d = 3.1d;
         * System.out.println
         * (Integer.toBinaryString(Float.floatToRawIntBits(f)));
         * System.out.println
         * (Long.toBinaryString(Double.doubleToRawLongBits(df)));
         * System.out.println
         * (Long.toBinaryString(Double.doubleToRawLongBits(d)));
         * 
         * - Float:
         * seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm
         * 1000000010001100110011001100110
         * - Double
         * seeeeeeeeeeemmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
         * 10000000   10001100110011001100110
         * 100000000001000110011001100110011000000000000000000000000000000
         * 100000000001000110011001100110011001100110011001100110011001101
         */

        /**
        * Examples of well documented but probably unexpected behavior in 
        * java / with 32-bit float to 64-bit float conversion.
        */
        assertFalse("Document unexpected behaviour with explicit type-casting float as double!", (double)0.2f == 0.2d );
        assertFalse("Document unexpected behaviour with implicit type-cast!", 0.2f == 0.2d );
        Double d1 = new Double( 1.1f );
        Double d2 = new Double( "1.1f" );
        assertFalse( "Document implicit type cast from float to double before calling Double(double d) constructor", d1.equals( d2 ) );

        assertTrue( "Correctly converting float to double via base10 (string) representation!", new Double( 3.1d ).equals(  new Double( new Float( 3.1f ).toString() ) ) );

        // Pinpointing the not so obvious "buggy" conversion from float to double in JSONObject
        JSONObject jo = new JSONObject();
        jo.put( "bug", 3.1f ); // will call put( String key, double value ) with implicit and "buggy" type-cast from float to double
        assertFalse( "The java-compiler did add some zero bits for you to the mantissa (unexpected, but well documented)", jo.get( "bug" ).equals(  new Double( 3.1d ) ) );

        JSONObject inc = new JSONObject();
        inc.put( "bug", new Float( 3.1f ) ); // This will put in instance of Float into JSONObject, i.e. call put( String key, Object value )
        assertTrue( "Everything is ok here!", inc.get( "bug" ) instanceof Float );
        inc.increment( "bug" ); // after adding 1, increment will call put( String key, double value ) with implicit and "buggy" type-cast from float to double!
        // this.put(key, (Float) value + 1);
        // 1.        The (Object)value will be typecasted to (Float)value since it is an instanceof Float actually nothing is done. 
        // 2.        Float instance will be autoboxed into float because the + operator will work on primitives not Objects!
        // 3.        A float+float operation will be performed and results into a float primitive.
        // 4.        There is no method that matches the signature put( String key, float value), java-compiler will choose the method
        //                put( String key, double value) and does an implicit type-cast(!) by appending zero-bits to the mantissa
        assertTrue( "JSONObject increment converts Float to Double", jo.get( "bug" ) instanceof Float );
        // correct implementation (with change of behavior) would be:
        // this.put(key, new Float((Float) value + 1)); 
        // Probably it would be better to deprecate the method and remove some day, while convenient processing the "payload" is not
        // really in the scope of a JSON-library (IMHO.)

    }

    /**
     * Exercise JSONObject numberToString() method
     */
    @SuppressWarnings("boxing")
    @Test
    public void jsonObjectNumberToString() {
        String str;
        Double dVal;
        Integer iVal = 1;
        str = JSONObject.numberToString(iVal);
        assertTrue("expected "+iVal+" actual "+str, iVal.toString().equals(str));
        dVal = 12.34;
        str = JSONObject.numberToString(dVal);
        assertTrue("expected "+dVal+" actual "+str, dVal.toString().equals(str));
        dVal = 12.34e27;
        str = JSONObject.numberToString(dVal);
        assertTrue("expected "+dVal+" actual "+str, dVal.toString().equals(str));
        // trailing .0 is truncated, so it doesn't quite match toString()
        dVal = 5000000.0000000;
        str = JSONObject.numberToString(dVal);
        assertTrue("expected 5000000 actual "+str, str.equals("5000000"));
    }

    /**
     * Exercise JSONObject put() and similar() methods
     */
    @SuppressWarnings("boxing")
    @Test
    public void jsonObjectPut() {
        String expectedStr = 
            "{"+
                "\"trueKey\":true,"+
                "\"falseKey\":false,"+
                "\"arrayKey\":[0,1,2],"+
                "\"objectKey\":{"+
                    "\"myKey1\":\"myVal1\","+
                    "\"myKey2\":\"myVal2\","+
                    "\"myKey3\":\"myVal3\","+
                    "\"myKey4\":\"myVal4\""+
                "}"+
            "}";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("trueKey", true);
        jsonObject.put("falseKey", false);
        Integer [] intArray = { 0, 1, 2 };
        jsonObject.put("arrayKey", Arrays.asList(intArray));
        Map<String, Object> myMap = new HashMap<String, Object>();
        myMap.put("myKey1", "myVal1");
        myMap.put("myKey2", "myVal2");
        myMap.put("myKey3", "myVal3");
        myMap.put("myKey4", "myVal4");
        jsonObject.put("objectKey", myMap);

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 4 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 4);
        assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
        assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
        assertTrue("expected 3 arrayKey items", ((List<?>)(JsonPath.read(doc, "$.arrayKey"))).size() == 3);
        assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0")));
        assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1")));
        assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2")));
        assertTrue("expected 4 objectKey items", ((Map<?,?>)(JsonPath.read(doc, "$.objectKey"))).size() == 4);
        assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1")));
        assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2")));
        assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3")));
        assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4")));

        jsonObject.remove("trueKey");
        JSONObject expectedJsonObject = new JSONObject(expectedStr);
        assertTrue("unequal jsonObjects should not be similar",
                !jsonObject.similar(expectedJsonObject));
        assertTrue("jsonObject should not be similar to jsonArray",
                !jsonObject.similar(new JSONArray()));

        String aCompareValueStr = "{\"a\":\"aval\",\"b\":true}";
        String bCompareValueStr = "{\"a\":\"notAval\",\"b\":true}";
        JSONObject aCompareValueJsonObject = new JSONObject(aCompareValueStr);
        JSONObject bCompareValueJsonObject = new JSONObject(bCompareValueStr);
        assertTrue("different values should not be similar",
                !aCompareValueJsonObject.similar(bCompareValueJsonObject));

        String aCompareObjectStr = "{\"a\":\"aval\",\"b\":{}}";
        String bCompareObjectStr = "{\"a\":\"aval\",\"b\":true}";
        JSONObject aCompareObjectJsonObject = new JSONObject(aCompareObjectStr);
        JSONObject bCompareObjectJsonObject = new JSONObject(bCompareObjectStr);
        assertTrue("different nested JSONObjects should not be similar",
                !aCompareObjectJsonObject.similar(bCompareObjectJsonObject));

        String aCompareArrayStr = "{\"a\":\"aval\",\"b\":[]}";
        String bCompareArrayStr = "{\"a\":\"aval\",\"b\":true}";
        JSONObject aCompareArrayJsonObject = new JSONObject(aCompareArrayStr);
        JSONObject bCompareArrayJsonObject = new JSONObject(bCompareArrayStr);
        assertTrue("different nested JSONArrays should not be similar",
                !aCompareArrayJsonObject.similar(bCompareArrayJsonObject));
    }

    /**
     * Exercise JSONObject toString() method
     */
    @Test
    public void jsonObjectToString() {
        String str = 
            "{"+
                "\"trueKey\":true,"+
                "\"falseKey\":false,"+
                "\"arrayKey\":[0,1,2],"+
                "\"objectKey\":{"+
                    "\"myKey1\":\"myVal1\","+
                    "\"myKey2\":\"myVal2\","+
                    "\"myKey3\":\"myVal3\","+
                    "\"myKey4\":\"myVal4\""+
                "}"+
            "}";
        JSONObject jsonObject = new JSONObject(str);

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 4 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 4);
        assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
        assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
        assertTrue("expected 3 arrayKey items", ((List<?>)(JsonPath.read(doc, "$.arrayKey"))).size() == 3);
        assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0")));
        assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1")));
        assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2")));
        assertTrue("expected 4 objectKey items", ((Map<?,?>)(JsonPath.read(doc, "$.objectKey"))).size() == 4);
        assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1")));
        assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2")));
        assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3")));
        assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4")));
    }

    /**
     * Exercise JSONObject toString() method with various indent levels.
     */
    @Test
    public void jsonObjectToStringIndent() {
        String jsonObject0Str =
                "{"+
                        "\"key1\":" +
                                "[1,2," +
                                        "{\"key3\":true}" +
                                "],"+
                        "\"key2\":" +
                                "{\"key1\":\"val1\",\"key2\":" +
                                        "{\"key2\":\"val2\"}" +
                                "},"+
                        "\"key3\":" +
                                "[" +
                                        "[1,2.1]" +
                                "," +
                                        "[null]" +
                                "]"+
                        "}";

        String jsonObject1Str =
                "{\n" +
                " \"key1\": [\n" +
                "  1,\n" +
                "  2,\n" +
                "  {\"key3\": true}\n" +
                " ],\n" +
                " \"key2\": {\n" +
                "  \"key1\": \"val1\",\n" +
                "  \"key2\": {\"key2\": \"val2\"}\n" +
                " },\n" +
                " \"key3\": [\n" +
                "  [\n" +
                "   1,\n" +
                "   2.1\n" +
                "  ],\n" +
                "  [null]\n" +
                " ]\n" +
                "}";
        String jsonObject4Str =
                "{\n" +
                "    \"key1\": [\n" +
                "        1,\n" +
                "        2,\n" +
                "        {\"key3\": true}\n" +
                "    ],\n" +
                "    \"key2\": {\n" +
                "        \"key1\": \"val1\",\n" +
                "        \"key2\": {\"key2\": \"val2\"}\n" +
                "    },\n" +
                "    \"key3\": [\n" +
                "        [\n" +
                "            1,\n" +
                "            2.1\n" +
                "        ],\n" +
                "        [null]\n" +
                "    ]\n" +
                "}";
        JSONObject jsonObject = new JSONObject(jsonObject0Str);
        // contents are tested in other methods, in this case just validate the spacing by
        // checking length
        assertEquals("toString() length",jsonObject0Str.length(), jsonObject.toString().length());
        assertEquals("toString(0) length",jsonObject0Str.length(), jsonObject.toString(0).length());
        assertEquals("toString(1) length",jsonObject1Str.length(), jsonObject.toString(1).length());
        assertEquals("toString(4) length",jsonObject4Str.length(), jsonObject.toString(4).length());
        
        JSONObject jo = new JSONObject().put("TABLE", new JSONObject().put("yhoo", new JSONObject()));
        assertEquals("toString(2)","{\"TABLE\": {\"yhoo\": {}}}", jo.toString(2));
    }

    /**
     * Explores how JSONObject handles maps. Insert a string/string map
     * as a value in a JSONObject. It will remain a map. Convert the 
     * JSONObject to string, then create a new JSONObject from the string. 
     * In the new JSONObject, the value will be stored as a nested JSONObject.
     * Confirm that map and nested JSONObject have the same contents.
     */
    @Test
    public void jsonObjectToStringSuppressWarningOnCastToMap() {
        JSONObject jsonObject = new JSONObject();
        Map<String, String> map = new HashMap();
        map.put("abc", "def");
        jsonObject.put("key", map);

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 1 top level item", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 1);
        assertTrue("expected 1 key item", ((Map<?,?>)(JsonPath.read(doc, "$.key"))).size() == 1);
        assertTrue("expected def", "def".equals(jsonObject.query("/key/abc")));
    }

    /**
     * Explores how JSONObject handles collections. Insert a string collection
     * as a value in a JSONObject. It will remain a collection. Convert the 
     * JSONObject to string, then create a new JSONObject from the string. 
     * In the new JSONObject, the value will be stored as a nested JSONArray.
     * Confirm that collection and nested JSONArray have the same contents.
     */
    @Test
    public void jsonObjectToStringSuppressWarningOnCastToCollection() {
        JSONObject jsonObject = new JSONObject();
        Collection<String> collection = new ArrayList<String>();
        collection.add("abc");
        // ArrayList will be added as an object
        jsonObject.put("key", collection);

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
        assertTrue("expected 1 top level item", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 1);
        assertTrue("expected 1 key item", ((List<?>)(JsonPath.read(doc, "$.key"))).size() == 1);
        assertTrue("expected abc", "abc".equals(jsonObject.query("/key/0")));
    }

    /**
     * Exercises the JSONObject.valueToString() method for various types
     */
    @Test
    public void valueToString() {
        
        assertTrue("null valueToString() incorrect",
                "null".equals(JSONObject.valueToString(null)));
        MyJsonString jsonString = new MyJsonString();
        assertTrue("jsonstring valueToString() incorrect",
                "my string".equals(JSONObject.valueToString(jsonString)));
        assertTrue("boolean valueToString() incorrect",
                "true".equals(JSONObject.valueToString(Boolean.TRUE)));
        assertTrue("non-numeric double",
                "null".equals(JSONObject.doubleToString(Double.POSITIVE_INFINITY)));
        String jsonObjectStr = 
            "{"+
                "\"key1\":\"val1\","+
                "\"key2\":\"val2\","+
                "\"key3\":\"val3\""+
             "}";
        JSONObject jsonObject = new JSONObject(jsonObjectStr);
        assertTrue("jsonObject valueToString() incorrect",
                JSONObject.valueToString(jsonObject).equals(jsonObject.toString()));
        String jsonArrayStr = 
            "[1,2,3]";
        JSONArray jsonArray = new JSONArray(jsonArrayStr);
        assertTrue("jsonArray valueToString() incorrect",
                JSONObject.valueToString(jsonArray).equals(jsonArray.toString()));
        Map<String, String> map = new HashMap<String, String>();
        map.put("key1", "val1");
        map.put("key2", "val2");
        map.put("key3", "val3");
        assertTrue("map valueToString() incorrect",
                jsonObject.toString().equals(JSONObject.valueToString(map))); 
        Collection<Integer> collection = new ArrayList<Integer>();
        collection.add(new Integer(1));
        collection.add(new Integer(2));
        collection.add(new Integer(3));
        assertTrue("collection valueToString() expected: "+
                jsonArray.toString()+ " actual: "+
                JSONObject.valueToString(collection),
                jsonArray.toString().equals(JSONObject.valueToString(collection))); 
        Integer[] array = { new Integer(1), new Integer(2), new Integer(3) };
        assertTrue("array valueToString() incorrect",
                jsonArray.toString().equals(JSONObject.valueToString(array))); 
    }

    /**
     * Confirm that https://github.com/douglascrockford/JSON-java/issues/167 is fixed.
     * The following code was throwing a ClassCastException in the 
     * JSONObject(Map<String, Object>) constructor
     */
    @SuppressWarnings("boxing")
    @Test
    public void valueToStringConfirmException() {
        Map<Integer, String> myMap = new HashMap<Integer, String>();
        myMap.put(1,  "myValue");
        // this is the test, it should not throw an exception
        String str = JSONObject.valueToString(myMap);
        // confirm result, just in case
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(str);
        assertTrue("expected 1 top level item", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 1);
        assertTrue("expected myValue", "myValue".equals(JsonPath.read(doc, "$.1")));
    }

    /**
     * Exercise the JSONObject wrap() method. Sometimes wrap() will change
     * the object being wrapped, other times not. The purpose of wrap() is
     * to ensure the value is packaged in a way that is compatible with how
     * a JSONObject value or JSONArray value is supposed to be stored.
     */
    @Test
    public void wrapObject() {
        // wrap(null) returns NULL
        assertTrue("null wrap() incorrect",
                JSONObject.NULL == JSONObject.wrap(null));

        // wrap(Integer) returns Integer
        Integer in = new Integer(1);
        assertTrue("Integer wrap() incorrect",
                in == JSONObject.wrap(in));

        /**
         * This test is to document the preferred behavior if BigDecimal is
         * supported. Previously bd returned as a string, since it
         * is recognized as being a Java package class. Now with explicit
         * support for big numbers, it remains a BigDecimal 
         */
        Object bdWrap = JSONObject.wrap(BigDecimal.ONE);
        assertTrue("BigDecimal.ONE evaluates to ONE",
                bdWrap.equals(BigDecimal.ONE));

        // wrap JSONObject returns JSONObject
        String jsonObjectStr = 
                "{"+
                    "\"key1\":\"val1\","+
                    "\"key2\":\"val2\","+
                    "\"key3\":\"val3\""+
                 "}";
        JSONObject jsonObject = new JSONObject(jsonObjectStr);
        assertTrue("JSONObject wrap() incorrect",
                jsonObject == JSONObject.wrap(jsonObject));

        // wrap collection returns JSONArray
        Collection<Integer> collection = new ArrayList<Integer>();
        collection.add(new Integer(1));
        collection.add(new Integer(2));
        collection.add(new Integer(3));
        JSONArray jsonArray = (JSONArray) (JSONObject.wrap(collection));

        // validate JSON
        Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
        assertTrue("expected 3 top level items", ((List<?>)(JsonPath.read(doc, "$"))).size() == 3);
        assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
        assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
        assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));

        // wrap Array returns JSONArray
        Integer[] array = { new Integer(1), new Integer(2), new Integer(3) };
        JSONArray integerArrayJsonArray = (JSONArray)(JSONObject.wrap(array));

        // validate JSON
        doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
        assertTrue("expected 3 top level items", ((List<?>)(JsonPath.read(doc, "$"))).size() == 3);
        assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
        assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
        assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));

        // validate JSON
        doc = Configuration.defaultConfiguration().jsonProvider().parse(integerArrayJsonArray.toString());
        assertTrue("expected 3 top level items", ((List<?>)(JsonPath.read(doc, "$"))).size() == 3);
        assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
        assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
        assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));

        // wrap map returns JSONObject
        Map<String, String> map = new HashMap<String, String>();
        map.put("key1", "val1");
        map.put("key2", "val2");
        map.put("key3", "val3");
        JSONObject mapJsonObject = (JSONObject) (JSONObject.wrap(map));

        // validate JSON
        doc = Configuration.defaultConfiguration().jsonProvider().parse(mapJsonObject.toString());
        assertTrue("expected 3 top level items", ((Map<?,?>)(JsonPath.read(doc, "$"))).size() == 3);
        assertTrue("expected val1", "val1".equals(mapJsonObject.query("/key1")));
        assertTrue("expected val2", "val2".equals(mapJsonObject.query("/key2")));
        assertTrue("expected val3", "val3".equals(mapJsonObject.query("/key3")));
    }

    
    /**
     * RFC 7159 defines control characters to be U+0000 through U+001F. This test verifies that the parser is checking for these in expected ways.
     */
    @Test
    public void jsonObjectParseControlCharacters(){
        for(int i = 0;i<=0x001f;i++){
            final String charString = String.valueOf((char)i);
            final String source = "{\"key\":\""+charString+"\"}";
            try {
                JSONObject jo = new JSONObject(source);
                assertTrue("Expected "+charString+"("+i+") in the JSON Object but did not find it.",charString.equals(jo.getString("key")));
            } catch (JSONException ex) {
                assertTrue("Only \\0 (U+0000), \\n (U+000A), and \\r (U+000D) should cause an error. Instead "+charString+"("+i+") caused an error",
                        i=='\0' || i=='\n' || i=='\r'
                );
            }
        }
    }

    /**
     * Explore how JSONObject handles parsing errors.
     */
    @SuppressWarnings({"boxing", "unused"})
    @Test
    public void jsonObjectParsingErrors() {
        try {
            // does not start with '{'
            String str = "abc";
            assertNull("Expected an exception",new JSONObject(str));
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "A JSONObject text must begin with '{' at 1 [character 2 line 1]",
                    e.getMessage());
        }
        try {
            // does not end with '}'
            String str = "{";
            assertNull("Expected an exception",new JSONObject(str));
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "A JSONObject text must end with '}' at 1 [character 2 line 1]",
                    e.getMessage());
        }
        try {
            // key with no ':'
            String str = "{\"myKey\" = true}";
            assertNull("Expected an exception",new JSONObject(str));
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "Expected a ':' after a key at 10 [character 11 line 1]",
                    e.getMessage());
        }
        try {
            // entries with no ',' separator
            String str = "{\"myKey\":true \"myOtherKey\":false}";
            assertNull("Expected an exception",new JSONObject(str));
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "Expected a ',' or '}' at 15 [character 16 line 1]",
                    e.getMessage());
        }
        try {
            // append to wrong key
            String str = "{\"myKey\":true, \"myOtherKey\":false}";
            JSONObject jsonObject = new JSONObject(str);
            jsonObject.append("myKey", "hello");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message",
                    "JSONObject[\"myKey\"] is not a JSONArray (null).",
                    e.getMessage());
        }
        try {
            // increment wrong key
            String str = "{\"myKey\":true, \"myOtherKey\":false}";
            JSONObject jsonObject = new JSONObject(str);
            jsonObject.increment("myKey");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message",
                    "Unable to increment [\"myKey\"].",
                    e.getMessage());
        }
        try {
            // invalid key
            String str = "{\"myKey\":true, \"myOtherKey\":false}";
            JSONObject jsonObject = new JSONObject(str);
            jsonObject.get(null);
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message",
                    "Null key.",
                    e.getMessage());
        }
        try {
            // invalid numberToString()
            JSONObject.numberToString((Number)null);
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertEquals("Expecting an exception message", 
                    "Null pointer",
                    e.getMessage());
        }

        try {
            // multiple putOnce key 
            JSONObject jsonObject = new JSONObject("{}");
            jsonObject.putOnce("hello", "world");
            jsonObject.putOnce("hello", "world!");
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertTrue("", true);
        }
        try {
            // test validity of invalid double 
            JSONObject.testValidity(Double.NaN);
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertTrue("", true);
        }
        try {
            // test validity of invalid float 
            JSONObject.testValidity(Float.NEGATIVE_INFINITY);
            fail("Expected an exception");
        } catch (JSONException e) { 
            assertTrue("", true);
        }
        try {
            // test exception message when including a duplicate key (level 0)
            String str = "{\n"
                        +"    \"attr01\":\"value-01\",\n"
                        +"    \"attr02\":\"value-02\",\n"
                        +"    \"attr03\":\"value-03\",\n"
                        +"    \"attr03\":\"value-04\"\n"
                        + "}";
            new JSONObject(str);
            fail("Expected an exception");
        } catch (JSONException e) {
            assertEquals("Expecting an expection message",
                    "Duplicate key \"attr03\" at 90 [character 13 line 5]",
                    e.getMessage());
        }
        try {
            // test exception message when including a duplicate key (level 0) holding an object
            String str = "{\n"
                        +"    \"attr01\":\"value-01\",\n"
                        +"    \"attr02\":\"value-02\",\n"
                        +"    \"attr03\":\"value-03\",\n"
                        +"    \"attr03\": {"
                        +"        \"attr04-01\":\"value-04-01\",n"
                        +"        \"attr04-02\":\"value-04-02\",n"
                        +"        \"attr04-03\":\"value-04-03\"n"
                        + "    }\n"
                        + "}";
            new JSONObject(str);
            fail("Expected an exception");
        } catch (JSONException e) {
            assertEquals("Expecting an expection message",
                    "Duplicate key \"attr03\" at 90 [character 13 line 5]",
                    e.getMessage());
        }
        try {
            // test exception message when including a duplicate key (level 0) holding an array
            String str = "{\n"
                        +"    \"attr01\":\"value-01\",\n"
                        +"    \"attr02\":\"value-02\",\n"
                        +"    \"attr03\":\"value-03\",\n"
                        +"    \"attr03\": [\n"
                        +"        {"
                        +"            \"attr04-01\":\"value-04-01\",n"
                        +"            \"attr04-02\":\"value-04-02\",n"
                        +"            \"attr04-03\":\"value-04-03\"n"
                        +"        }\n"
                        + "    ]\n"
                        + "}";
            new JSONObject(str);
            fail("Expected an exception");
        } catch (JSONException e) {
            assertEquals("Expecting an expection message",
                    "Duplicate key \"attr03\" at 90 [character 13 line 5]",
                    e.getMessage());
        }
        try {
            // test exception message when including a duplicate key (level 1)
            String str = "{\n"
                        +"    \"attr01\":\"value-01\",\n"
                        +"    \"attr02\":\"value-02\",\n"
                        +"    \"attr03\":\"value-03\",\n"
                        +"    \"attr04\": {\n"
                        +"        \"attr04-01\":\"value04-01\",\n"
                        +"        \"attr04-02\":\"value04-02\",\n"
                        +"        \"attr04-03\":\"value04-03\",\n"
                        +"        \"attr04-03\":\"value04-04\"\n"
                        + "    }\n"
                        + "}";
            new JSONObject(str);
            fail("Expected an exception");
        } catch (JSONException e) {
            assertEquals("Expecting an expection message",
                    "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
                    e.getMessage());
        }
        try {
            // test exception message when including a duplicate key (level 1) holding an object
            String str = "{\n"
                        +"    \"attr01\":\"value-01\",\n"
                        +"    \"attr02\":\"value-02\",\n"
                        +"    \"attr03\":\"value-03\",\n"
                        +"    \"attr04\": {\n"
                        +"        \"attr04-01\":\"value04-01\",\n"
                        +"        \"attr04-02\":\"value04-02\",\n"
                        +"        \"attr04-03\":\"value04-03\",\n"
                        +"        \"attr04-03\": {\n"
                        +"            \"attr04-04-01\":\"value04-04-01\",\n"
                        +"            \"attr04-04-02\":\"value04-04-02\",\n"
                        +"            \"attr04-04-03\":\"value04-04-03\",\n"
                        +"        }\n"
                        +"    }\n"
                        + "}";
            new JSONObject(str);
            fail("Expected an exception");
        } catch (JSONException e) {
            assertEquals("Expecting an expection message",
                    "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
                    e.getMessage());
        }
        try {
            // test exception message when including a duplicate key (level 1) holding an array
            String str = "{\n"
                        +"    \"attr01\":\"value-01\",\n"
                        +"    \"attr02\":\"value-02\",\n"
                        +"    \"attr03\":\"value-03\",\n"
                        +"    \"attr04\": {\n"
                        +"        \"attr04-01\":\"value04-01\",\n"
                        +"        \"attr04-02\":\"value04-02\",\n"
                        +"        \"attr04-03\":\"value04-03\",\n"
                        +"        \"attr04-03\": [\n"
                        +"            {\n"
                        +"                \"attr04-04-01\":\"value04-04-01\",\n"
                        +"                \"attr04-04-02\":\"value04-04-02\",\n"
                        +"                \"attr04-04-03\":\"value04-04-03\",\n"
                        +"            }\n"
                        +"        ]\n"
                        +"    }\n"
                        + "}";
            new JSONObject(str);
            fail("Expected an exception");
        } catch (JSONException e) {
            assertEquals("Expecting an expection message",
                    "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
                    e.getMessage());
        }
        try {
            // test exception message when including a duplicate key in object (level 0) within an array
            String str = "[\n"
                        +"    {\n"
                        +"        \"attr01\":\"value-01\",\n"
                        +"        \"attr02\":\"value-02\"\n"
                        +"    },\n"
                        +"    {\n"
                        +"        \"attr01\":\"value-01\",\n"
                        +"        \"attr01\":\"value-02\"\n"
                        +"    }\n"
                        + "]";
            new JSONArray(str);
            fail("Expected an exception");
        } catch (JSONException e) {
            assertEquals("Expecting an expection message",
                    "Duplicate key \"attr01\" at 124 [character 17 line 8]",
                    e.getMessage());
        }
        try {
            // test exception message when including a duplicate key in object (level 1) within an array
            String str = "[\n"
                        +"    {\n"
                        +"        \"attr01\":\"value-01\",\n"
                        +"        \"attr02\": {\n"
                        +"            \"attr02-01\":\"value-02-01\",\n"
                        +"            \"attr02-02\":\"value-02-02\"\n"
                        +"        }\n"
                        +"    },\n"
                        +"    {\n"
                        +"        \"attr01\":\"value-01\",\n"
                        +"        \"attr02\": {\n"
                        +"            \"attr02-01\":\"value-02-01\",\n"
                        +"            \"attr02-01\":\"value-02-02\"\n"
                        +"        }\n"
                        +"    }\n"
                        + "]";
            new JSONArray(str);
            fail("Expected an exception");
        } catch (JSONException e) {
            assertEquals("Expecting an expection message",
                    "Duplicate key \"attr02-01\" at 269 [character 24 line 13]",
                    e.getMessage());
        }
    }

    /**
     * Confirm behavior when putOnce() is called with null parameters
     */
    @Test
    public void jsonObjectPutOnceNull() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.putOnce(null, null);
        assertTrue("jsonObject should be empty", jsonObject.isEmpty());
        jsonObject.putOnce("", null);
        assertTrue("jsonObject should be empty", jsonObject.isEmpty());
        jsonObject.putOnce(null, "");
        assertTrue("jsonObject should be empty", jsonObject.isEmpty());
    }

    /**
     * Exercise JSONObject opt(key, default) method.
     */
    @Test
    public void jsonObjectOptDefault() {

        String str = "{\"myKey\": \"myval\", \"hiKey\": null}";
        JSONObject jsonObject = new JSONObject(str);

        assertTrue("optBigDecimal() should return default BigDecimal",
                BigDecimal.TEN.compareTo(jsonObject.optBigDecimal("myKey", BigDecimal.TEN))==0);
        assertTrue("optBigInteger() should return default BigInteger",
                BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0);
        assertTrue("optBoolean() should return default boolean",
                 jsonObject.optBoolean("myKey", true));
        assertTrue("optInt() should return default int",
                42 == jsonObject.optInt("myKey", 42));
        assertTrue("optEnum() should return default Enum",
                MyEnum.VAL1.equals(jsonObject.optEnum(MyEnum.class, "myKey", MyEnum.VAL1)));
        assertTrue("optJSONArray() should return null ",
                null==jsonObject.optJSONArray("myKey"));
        assertTrue("optJSONObject() should return default JSONObject ",
                jsonObject.optJSONObject("myKey", new JSONObject("{\"testKey\":\"testValue\"}")).getString("testKey").equals("testValue"));
        assertTrue("optLong() should return default long",
                42l == jsonObject.optLong("myKey", 42l));
        assertTrue("optDouble() should return default double",
                42.3d == jsonObject.optDouble("myKey", 42.3d));
        assertTrue("optFloat() should return default float",
                42.3f == jsonObject.optFloat("myKey", 42.3f));
        assertTrue("optNumber() should return default Number",
                42l == jsonObject.optNumber("myKey", Long.valueOf(42)).longValue());
        assertTrue("optString() should return default string",
                "hi".equals(jsonObject.optString("hiKey", "hi")));
    }
    
    /**
     * Exercise JSONObject opt(key, default) method when the key doesn't exist.
     */
    @Test
    public void jsonObjectOptNoKey() {

         JSONObject jsonObject = new JSONObject();
         
         assertNull(jsonObject.opt(null));

         assertTrue("optBigDecimal() should return default BigDecimal",
                 BigDecimal.TEN.compareTo(jsonObject.optBigDecimal("myKey", BigDecimal.TEN))==0);
         assertTrue("optBigInteger() should return default BigInteger",
                 BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0);
         assertTrue("optBoolean() should return default boolean",
                  jsonObject.optBoolean("myKey", true));
         assertTrue("optInt() should return default int",
                 42 == jsonObject.optInt("myKey", 42));
         assertTrue("optEnum() should return default Enum",
                 MyEnum.VAL1.equals(jsonObject.optEnum(MyEnum.class, "myKey", MyEnum.VAL1)));
         assertTrue("optJSONArray() should return null ",
                 null==jsonObject.optJSONArray("myKey"));
         assertTrue("optJSONObject() should return default JSONObject ",
                jsonObject.optJSONObject("myKey", new JSONObject("{\"testKey\":\"testValue\"}")).getString("testKey").equals("testValue"));
         assertTrue("optLong() should return default long",
                 42l == jsonObject.optLong("myKey", 42l));
         assertTrue("optDouble() should return default double",
                 42.3d == jsonObject.optDouble("myKey", 42.3d));
         assertTrue("optFloat() should return default float",
                 42.3f == jsonObject.optFloat("myKey", 42.3f));
         assertTrue("optNumber() should return default Number",
                 42l == jsonObject.optNumber("myKey", Long.valueOf(42)).longValue());
         assertTrue("optString() should return default string",
                 "hi".equals(jsonObject.optString("hiKey", "hi")));
    }
    
    /**
     * Verifies that the opt methods properly convert string values.
     */
    @Test
    public void jsonObjectOptStringConversion() {
        JSONObject jo = new JSONObject("{\"int\":\"123\",\"true\":\"true\",\"false\":\"false\"}");
        assertTrue("unexpected optBoolean value",jo.optBoolean("true",false)==true);
        assertTrue("unexpected optBoolean value",jo.optBoolean("false",true)==false);
        assertTrue("unexpected optInt value",jo.optInt("int",0)==123);
        assertTrue("unexpected optLong value",jo.optLong("int",0)==123l);
        assertTrue("unexpected optDouble value",jo.optDouble("int",0.0d)==123.0d);
        assertTrue("unexpected optFloat value",jo.optFloat("int",0.0f)==123.0f);
        assertTrue("unexpected optBigInteger value",jo.optBigInteger("int",BigInteger.ZERO).compareTo(new BigInteger("123"))==0);
        assertTrue("unexpected optBigDecimal value",jo.optBigDecimal("int",BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0);
        assertTrue("unexpected optBigDecimal value",jo.optBigDecimal("int",BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0);
        assertTrue("unexpected optNumber value",jo.optNumber("int",BigInteger.ZERO).longValue()==123l);
    }
    
    /**
     * Verifies that the opt methods properly convert string values to numbers and coerce them consistently.
     */
    @Test
    public void jsonObjectOptCoercion() {
        JSONObject jo = new JSONObject("{\"largeNumberStr\":\"19007199254740993.35481234487103587486413587843213584\"}");
        // currently the parser doesn't recognize BigDecimal, to we have to put it manually
        jo.put("largeNumber", new BigDecimal("19007199254740993.35481234487103587486413587843213584"));
        
        // Test type coercion from larger to smaller
        assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"), jo.optBigDecimal("largeNumber",null));
        assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumber",null));
        assertEquals(1.9007199254740992E16, jo.optDouble("largeNumber"),0.0);
        assertEquals(1.90071995E16f, jo.optFloat("largeNumber"),0.0f);
        assertEquals(19007199254740993l, jo.optLong("largeNumber"));
        assertEquals(1874919425, jo.optInt("largeNumber"));

        // conversion from a string
        assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"), jo.optBigDecimal("largeNumberStr",null));
        assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumberStr",null));
        assertEquals(1.9007199254740992E16, jo.optDouble("largeNumberStr"),0.0);
        assertEquals(1.90071995E16f, jo.optFloat("largeNumberStr"),0.0f);
        assertEquals(19007199254740993l, jo.optLong("largeNumberStr"));
        assertEquals(1874919425, jo.optInt("largeNumberStr"));

        // the integer portion of the actual value is larger than a double can hold.
        assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumber"));
        assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumber"));
        assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumberStr"));
        assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumberStr"));
        assertEquals(19007199254740992l, (long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"));
        assertEquals(2147483647, (int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"));
    }
    
    /**
     * Verifies that the optBigDecimal method properly converts values to BigDecimal and coerce them consistently.
     */
    @Test
    public void jsonObjectOptBigDecimal() {
        JSONObject jo = new JSONObject().put("int", 123).put("long", 654L)
                .put("float", 1.234f).put("double", 2.345d)
                .put("bigInteger", new BigInteger("1234"))
                .put("bigDecimal", new BigDecimal("1234.56789"))
                .put("nullVal", JSONObject.NULL);
        
        assertEquals(new BigDecimal("123"),jo.optBigDecimal("int", null));
        assertEquals(new BigDecimal("654"),jo.optBigDecimal("long", null));
        assertEquals(new BigDecimal(1.234f),jo.optBigDecimal("float", null));
        assertEquals(new BigDecimal(2.345d),jo.optBigDecimal("double", null));
        assertEquals(new BigDecimal("1234"),jo.optBigDecimal("bigInteger", null));
        assertEquals(new BigDecimal("1234.56789"),jo.optBigDecimal("bigDecimal", null));
        assertNull(jo.optBigDecimal("nullVal", null));
        assertEquals(jo.optBigDecimal("float", null),jo.getBigDecimal("float"));
        assertEquals(jo.optBigDecimal("double", null),jo.getBigDecimal("double"));
    }
    
    /**
     * Verifies that the optBigDecimal method properly converts values to BigDecimal and coerce them consistently.
     */
    @Test
    public void jsonObjectOptBigInteger() {
        JSONObject jo = new JSONObject().put("int", 123).put("long", 654L)
                .put("float", 1.234f).put("double", 2.345d)
                .put("bigInteger", new BigInteger("1234"))
                .put("bigDecimal", new BigDecimal("1234.56789"))
                .put("nullVal", JSONObject.NULL);
        
        assertEquals(new BigInteger("123"),jo.optBigInteger("int", null));
        assertEquals(new BigInteger("654"),jo.optBigInteger("long", null));
        assertEquals(new BigInteger("1"),jo.optBigInteger("float", null));
        assertEquals(new BigInteger("2"),jo.optBigInteger("double", null));
        assertEquals(new BigInteger("1234"),jo.optBigInteger("bigInteger", null));
        assertEquals(new BigInteger("1234"),jo.optBigInteger("bigDecimal", null));
        assertNull(jo.optBigDecimal("nullVal", null));
    }

    /**
     * Confirm behavior when JSONObject put(key, null object) is called
     */
    @Test
    public void jsonObjectputNull() {

        // put null should remove the item.
        String str = "{\"myKey\": \"myval\"}";
        JSONObject jsonObjectRemove = new JSONObject(str);
        jsonObjectRemove.remove("myKey");
        assertTrue("jsonObject should be empty", jsonObjectRemove.isEmpty());

        JSONObject jsonObjectPutNull = new JSONObject(str);
        jsonObjectPutNull.put("myKey", (Object) null);
        assertTrue("jsonObject should be empty", jsonObjectPutNull.isEmpty());


    }

    /**
     * Exercise JSONObject quote() method
     * This purpose of quote() is to ensure that for strings with embedded
     * quotes, the quotes are properly escaped.
     */
    @Test
    public void jsonObjectQuote() {
        String str;
        str = "";
        String quotedStr;
        quotedStr = JSONObject.quote(str);
        assertTrue("quote() expected escaped quotes, found "+quotedStr,
                "\"\"".equals(quotedStr));
        str = "\"\"";
        quotedStr = JSONObject.quote(str);
        assertTrue("quote() expected escaped quotes, found "+quotedStr,
                "\"\\\"\\\"\"".equals(quotedStr));
        str = "</";
        quotedStr = JSONObject.quote(str);
        assertTrue("quote() expected escaped frontslash, found "+quotedStr,
                "\"<\\/\"".equals(quotedStr));
        str = "AB\bC";
        quotedStr = JSONObject.quote(str);
        assertTrue("quote() expected escaped backspace, found "+quotedStr,
                "\"AB\\bC\"".equals(quotedStr));
        str = "ABC\n";
        quotedStr = JSONObject.quote(str);
        assertTrue("quote() expected escaped newline, found "+quotedStr,
                "\"ABC\\n\"".equals(quotedStr));
        str = "AB\fC";
        quotedStr = JSONObject.quote(str);
        assertTrue("quote() expected escaped formfeed, found "+quotedStr,
                "\"AB\\fC\"".equals(quotedStr));
        str = "\r";
        quotedStr = JSONObject.quote(str);
        assertTrue("quote() expected escaped return, found "+quotedStr,
                "\"\\r\"".equals(quotedStr));
        str = "\u1234\u0088";
        quotedStr = JSONObject.quote(str);
        assertTrue("quote() expected escaped unicode, found "+quotedStr,
                "\"\u1234\\u0088\"".equals(quotedStr));
    }

    /**
     * Confirm behavior when JSONObject stringToValue() is called for an
     * empty string
     */
    @Test
    public void stringToValue() {
        String str = "";
        String valueStr = (String)(JSONObject.stringToValue(str));
        assertTrue("stringToValue() expected empty String, found "+valueStr,
                "".equals(valueStr));
    }

    /**
     * Confirm behavior when toJSONArray is called with a null value
     */
    @Test
    public void toJSONArray() {
        assertTrue("toJSONArray() with null names should be null",
                null == new JSONObject().toJSONArray(null));
    }

    /**
     * Exercise the JSONObject write() method
     */
    @Test
    public void write() throws IOException {
        String str = "{\"key1\":\"value1\",\"key2\":[1,2,3]}";
        String expectedStr = str;
        JSONObject jsonObject = new JSONObject(str);
        StringWriter stringWriter = new StringWriter();
        try {
            String actualStr = jsonObject.write(stringWriter).toString();
            // key order may change. verify length and individual key content
            assertEquals("length", expectedStr.length(), actualStr.length());
            assertTrue("key1", actualStr.contains("\"key1\":\"value1\""));
            assertTrue("key2", actualStr.contains("\"key2\":[1,2,3]"));
        } finally {
            stringWriter.close();
        }
    }
    
    /**
     * Confirms that exceptions thrown when writing values are wrapped properly.
     */
    @Test
    public void testJSONWriterException() {
        final JSONObject jsonObject = new JSONObject();

        jsonObject.put("someKey",new BrokenToString());

        // test single element JSONObject
        StringWriter writer = new StringWriter();
        try {
            jsonObject.write(writer).toString();
            fail("Expected an exception, got a String value");
        } catch (JSONException e) {
            assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
        } catch(Exception e) {
            fail("Expected JSONException");
        } finally {
            try {
                writer.close();
            } catch (Exception e) {}
        }

        //test multiElement
        jsonObject.put("somethingElse", "a value");
        
        writer = new StringWriter();
        try {
            jsonObject.write(writer).toString();
            fail("Expected an exception, got a String value");
        } catch (JSONException e) {
            assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
        } catch(Exception e) {
            fail("Expected JSONException");
        } finally {
            try {
                writer.close();
            } catch (Exception e) {}
        }
        
        // test a more complex object
        writer = new StringWriter();
        try {
            new JSONObject()
                .put("somethingElse", "a value")
                .put("someKey", new JSONArray()
                        .put(new JSONObject().put("key1", new BrokenToString())))
                .write(writer).toString();
            fail("Expected an exception, got a String value");
        } catch (JSONException e) {
            assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
        } catch(Exception e) {
            fail("Expected JSONException");
        } finally {
            try {
                writer.close();
            } catch (Exception e) {}
        }
       
        // test a more slightly complex object
        writer = new StringWriter();
        try {
            new JSONObject()
                .put("somethingElse", "a value")
                .put("someKey", new JSONArray()
                        .put(new JSONObject().put("key1", new BrokenToString()))
                        .put(12345)
                 )
                .write(writer).toString();
            fail("Expected an exception, got a String value");
        } catch (JSONException e) {
            assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
        } catch(Exception e) {
            fail("Expected JSONException");
        } finally {
            try {
                writer.close();
            } catch (Exception e) {}
        }
       
    }


    /**
     * Exercise the JSONObject write() method
     */
/*
    @Test
    public void writeAppendable() {
        String str = "{\"key1\":\"value1\",\"key2\":[1,2,3]}";
        String expectedStr = str;
        JSONObject jsonObject = new JSONObject(str);
        StringBuilder stringBuilder = new StringBuilder();
        Appendable appendable = jsonObject.write(stringBuilder);
        String actualStr = appendable.toString();
        assertTrue("write() expected " +expectedStr+
                        " but found " +actualStr,
                expectedStr.equals(actualStr));
    }
*/

    /**
     * Exercise the JSONObject write(Writer, int, int) method
     */
    @Test
    public void write3Param() throws IOException {
        String str0 = "{\"key1\":\"value1\",\"key2\":[1,false,3.14]}";
        String str2 =
                "{\n" +
                "   \"key1\": \"value1\",\n" +
                "   \"key2\": [\n" +
                "     1,\n" +
                "     false,\n" +
                "     3.14\n" +
                "   ]\n" +
                " }";
        JSONObject jsonObject = new JSONObject(str0);
        StringWriter stringWriter = new StringWriter();
        try {
            String actualStr = jsonObject.write(stringWriter,0,0).toString();
            
            assertEquals("length", str0.length(), actualStr.length());
            assertTrue("key1", actualStr.contains("\"key1\":\"value1\""));
            assertTrue("key2", actualStr.contains("\"key2\":[1,false,3.14]"));
        } finally {
            try {
                stringWriter.close();
            } catch (Exception e) {}
        }
        
        stringWriter = new StringWriter();
        try {
            String actualStr = jsonObject.write(stringWriter,2,1).toString();

            assertEquals("length", str2.length(), actualStr.length());
            assertTrue("key1", actualStr.contains("   \"key1\": \"value1\""));
            assertTrue("key2", actualStr.contains("   \"key2\": [\n" +
                            "     1,\n" +
                            "     false,\n" +
                            "     3.14\n" +
                            "   ]")
            );
        } finally {
            try {
                stringWriter.close();
            } catch (Exception e) {}
        }
    }

    /**
     * Exercise the JSONObject write(Appendable, int, int) method
     */
/*
    @Test
    public void write3ParamAppendable() {
        String str0 = "{\"key1\":\"value1\",\"key2\":[1,false,3.14]}";
        String str2 =
                "{\n" +
                        "   \"key1\": \"value1\",\n" +
                        "   \"key2\": [\n" +
                        "     1,\n" +
                        "     false,\n" +
                        "     3.14\n" +
                        "   ]\n" +
                        " }";
        JSONObject jsonObject = new JSONObject(str0);
        String expectedStr = str0;
        StringBuilder stringBuilder = new StringBuilder();
        Appendable appendable = jsonObject.write(stringBuilder,0,0);
        String actualStr = appendable.toString();
        assertEquals(expectedStr, actualStr);

        expectedStr = str2;
        stringBuilder = new StringBuilder();
        appendable = jsonObject.write(stringBuilder,2,1);
        actualStr = appendable.toString();
        assertEquals(expectedStr, actualStr);
    }
*/

    /**
     * Exercise the JSONObject equals() method
     */
    @Test
    public void equals() {
        String str = "{\"key\":\"value\"}";
        JSONObject aJsonObject = new JSONObject(str);
        assertTrue("Same JSONObject should be equal to itself",
                aJsonObject.equals(aJsonObject));
    }

    /**
     * JSON null is not the same as Java null. This test examines the differences
     * in how they are handled by JSON-java.
     */
    @Test
    public void jsonObjectNullOperations() {
        /**
         * The Javadoc for JSONObject.NULL states:
         *      "JSONObject.NULL is equivalent to the value that JavaScript calls null,
         *      whilst Java's null is equivalent to the value that JavaScript calls
         *      undefined."
         * 
         * Standard ECMA-262 6th Edition / June 2015 (included to help explain the javadoc):
         *      undefined value: primitive value used when a variable has not been assigned a value
         *      Undefined type:  type whose sole value is the undefined value
         *      null value:      primitive value that represents the intentional absence of any object value
         *      Null type:       type whose sole value is the null value
         * Java SE8 language spec (included to help explain the javadoc):
         *      The Kinds of Types and Values ...
         *      There is also a special null type, the type of the expression null, which has no name.
         *      Because the null type has no name, it is impossible to declare a variable of the null 
         *      type or to cast to the null type. The null reference is the only possible value of an 
         *      expression of null type. The null reference can always be assigned or cast to any reference type.
         *      In practice, the programmer can ignore the null type and just pretend that null is merely 
         *      a special literal that can be of any reference type.
         * Extensible Markup Language (XML) 1.0 Fifth Edition / 26 November 2008
         *      No mention of null
         * ECMA-404 1st Edition / October 2013:
         *      JSON Text  ...
         *      These are three literal name tokens: ...
         *      null 
         * 
         * There seems to be no best practice to follow, it's all about what we
         * want the code to do.
         */

        // add JSONObject.NULL then convert to string in the manner of XML.toString() 
        JSONObject jsonObjectJONull = new JSONObject();
        Object obj = JSONObject.NULL;
        jsonObjectJONull.put("key", obj);
        Object value = jsonObjectJONull.opt("key");
        assertTrue("opt() JSONObject.NULL should find JSONObject.NULL",
                obj.equals(value));
        value = jsonObjectJONull.get("key");
        assertTrue("get() JSONObject.NULL should find JSONObject.NULL",
                obj.equals(value));
        if (value == null) {
            value = "";
        }
        String string = value instanceof String ? (String)value : null;
        assertTrue("XML toString() should convert JSONObject.NULL to null",
                string == null);

        // now try it with null
        JSONObject jsonObjectNull = new JSONObject();
        obj = null;
        jsonObjectNull.put("key", obj);
        value = jsonObjectNull.opt("key");
        assertNull("opt() null should find null", value);
        // what is this trying to do? It appears to test absolutely nothing...
//        if (value == null) {
//            value = "";
//        }
//        string = value instanceof String ? (String)value : null;
//        assertTrue("should convert null to empty string", "".equals(string));
        try {
            value = jsonObjectNull.get("key");
            fail("get() null should throw exception");
        } catch (Exception ignored) {}

        /**
         * XML.toString() then goes on to do something with the value
         * if the key val is "content", then value.toString() will be 
         * called. This will evaluate to "null" for JSONObject.NULL,
         * and the empty string for null.
         * But if the key is anything else, then JSONObject.NULL will be emitted
         * as <key>null</key> and null will be emitted as ""
         */
        String sJONull = XML.toString(jsonObjectJONull);
        assertTrue("JSONObject.NULL should emit a null value", 
                "<key>null</key>".equals(sJONull));
        String sNull = XML.toString(jsonObjectNull);
        assertTrue("null should emit an empty string", "".equals(sNull));
    }
    
    @Test(expected = JSONPointerException.class)
    public void queryWithNoResult() {
        new JSONObject().query("/a/b");
    }
    
    @Test
    public void optQueryWithNoResult() {
        assertNull(new JSONObject().optQuery("/a/b"));
    }
    
    @Test(expected = IllegalArgumentException.class)
    public void optQueryWithSyntaxError() {
        new JSONObject().optQuery("invalid");
    }

    @Test(expected = JSONException.class)
    public void invalidEscapeSequence() {
      String json = "{ \"\\url\": \"value\" }";
      assertNull("Expected an exception",new JSONObject(json));
    }

    /**
     * Exercise JSONObject toMap() method.
     */
    @Test
    public void toMap() {
        String jsonObjectStr =
                "{" +
                "\"key1\":" +
                    "[1,2," +
                        "{\"key3\":true}" +
                    "]," +
                "\"key2\":" +
                    "{\"key1\":\"val1\",\"key2\":" +
                        "{\"key2\":null}," +
                    "\"key3\":42" +
                    "}," +
                "\"key3\":" +
                    "[" +
                        "[\"value1\",2.1]" +
                    "," +
                        "[null]" +
                    "]" +
                "}";

        JSONObject jsonObject = new JSONObject(jsonObjectStr);
        Map<?,?> map = jsonObject.toMap();

        assertTrue("Map should not be null", map != null);
        assertTrue("Map should have 3 elements", map.size() == 3);

        List<?> key1List = (List<?>)map.get("key1");
        assertTrue("key1 should not be null", key1List != null);
        assertTrue("key1 list should have 3 elements", key1List.size() == 3);
        assertTrue("key1 value 1 should be 1", key1List.get(0).equals(Integer.valueOf(1)));
        assertTrue("key1 value 2 should be 2", key1List.get(1).equals(Integer.valueOf(2)));

        Map<?,?> key1Value3Map = (Map<?,?>)key1List.get(2);
        assertTrue("Map should not be null", key1Value3Map != null);
        assertTrue("Map should have 1 element", key1Value3Map.size() == 1);
        assertTrue("Map key3 should be true", key1Value3Map.get("key3").equals(Boolean.TRUE));

        Map<?,?> key2Map = (Map<?,?>)map.get("key2");
        assertTrue("key2 should not be null", key2Map != null);
        assertTrue("key2 map should have 3 elements", key2Map.size() == 3);
        assertTrue("key2 map key 1 should be val1", key2Map.get("key1").equals("val1"));
        assertTrue("key2 map key 3 should be 42", key2Map.get("key3").equals(Integer.valueOf(42)));

        Map<?,?> key2Val2Map = (Map<?,?>)key2Map.get("key2");
        assertTrue("key2 map key 2 should not be null", key2Val2Map != null);
        assertTrue("key2 map key 2 should have an entry", key2Val2Map.containsKey("key2"));
        assertTrue("key2 map key 2 value should be null", key2Val2Map.get("key2") == null);

        List<?> key3List = (List<?>)map.get("key3");
        assertTrue("key3 should not be null", key3List != null);
        assertTrue("key3 list should have 3 elements", key3List.size() == 2);

        List<?> key3Val1List = (List<?>)key3List.get(0);
        assertTrue("key3 list val 1 should not be null", key3Val1List != null);
        assertTrue("key3 list val 1 should have 2 elements", key3Val1List.size() == 2);
        assertTrue("key3 list val 1 list element 1 should be value1", key3Val1List.get(0).equals("value1"));
        assertTrue("key3 list val 1 list element 2 should be 2.1", key3Val1List.get(1).equals(new BigDecimal("2.1")));

        List<?> key3Val2List = (List<?>)key3List.get(1);
        assertTrue("key3 list val 2 should not be null", key3Val2List != null);
        assertTrue("key3 list val 2 should have 1 element", key3Val2List.size() == 1);
        assertTrue("key3 list val 2 list element 1 should be null", key3Val2List.get(0) == null);

        // Assert that toMap() is a deep copy
        jsonObject.getJSONArray("key3").getJSONArray(0).put(0, "still value 1");
        assertTrue("key3 list val 1 list element 1 should be value1", key3Val1List.get(0).equals("value1"));

        // assert that the new map is mutable
        assertTrue("Removing a key should succeed", map.remove("key3") != null);
        assertTrue("Map should have 2 elements", map.size() == 2);
    }
    
    /**
     * test that validates a singleton can be serialized as a bean.
     */
    @SuppressWarnings("boxing")
    @Test
    public void testSingletonBean() {
        final JSONObject jo = new JSONObject(Singleton.getInstance());
        assertEquals(jo.keySet().toString(), 1, jo.length());
        assertEquals(0, jo.get("someInt"));
        assertEquals(null, jo.opt("someString"));
        
        // Update the singleton values
        Singleton.getInstance().setSomeInt(42);
        Singleton.getInstance().setSomeString("Something");
        final JSONObject jo2 = new JSONObject(Singleton.getInstance());
        assertEquals(2, jo2.length());
        assertEquals(42, jo2.get("someInt"));
        assertEquals("Something", jo2.get("someString"));

        // ensure our original jo hasn't changed.
        assertEquals(0, jo.get("someInt"));
        assertEquals(null, jo.opt("someString"));
    }

    /**
     * test that validates a singleton can be serialized as a bean.
     */
    @SuppressWarnings("boxing")
    @Test
    public void testSingletonEnumBean() {
        final JSONObject jo = new JSONObject(SingletonEnum.getInstance());
        assertEquals(jo.keySet().toString(), 1, jo.length());
        assertEquals(0, jo.get("someInt"));
        assertEquals(null, jo.opt("someString"));
        
        // Update the singleton values
        SingletonEnum.getInstance().setSomeInt(42);
        SingletonEnum.getInstance().setSomeString("Something");
        final JSONObject jo2 = new JSONObject(SingletonEnum.getInstance());
        assertEquals(2, jo2.length());
        assertEquals(42, jo2.get("someInt"));
        assertEquals("Something", jo2.get("someString"));

        // ensure our original jo hasn't changed.
        assertEquals(0, jo.get("someInt"));
        assertEquals(null, jo.opt("someString"));
    }
    
    /**
     * Test to validate that a generic class can be serialized as a bean.
     */
    @SuppressWarnings("boxing")
    @Test
    public void testGenericBean() {
        GenericBean<Integer> bean = new GenericBean(42);
        final JSONObject jo = new JSONObject(bean);
        assertEquals(jo.keySet().toString(), 8, jo.length());
        assertEquals(42, jo.get("genericValue"));
        assertEquals("Expected the getter to only be called once",
                1, bean.genericGetCounter);
        assertEquals(0, bean.genericSetCounter);
    }
    
    /**
     * Test to validate that a generic class can be serialized as a bean.
     */
    @SuppressWarnings("boxing")
    @Test
    public void testGenericIntBean() {
        GenericBeanInt bean = new GenericBeanInt(42);
        final JSONObject jo = new JSONObject(bean);
        assertEquals(jo.keySet().toString(), 10, jo.length());
        assertEquals(42, jo.get("genericValue"));
        assertEquals("Expected the getter to only be called once",
                1, bean.genericGetCounter);
        assertEquals(0, bean.genericSetCounter);
    }
    
    /**
     * Test to verify <code>key</code> limitations in the JSONObject bean serializer.
     */
    @Test
    public void testWierdListBean() {
        @SuppressWarnings("boxing")
        WeirdList bean = new WeirdList(42, 43, 44);
        final JSONObject jo = new JSONObject(bean);
        // get() should have a key of 0 length
        // get(int) should be ignored base on parameter count
        // getInt(int) should also be ignored based on parameter count
        // add(Integer) should be ignore as it doesn't start with get/is and also has a parameter
        // getALL should be mapped
        assertEquals("Expected 1 key to be mapped. Instead found: "+jo.keySet().toString(),
                1, jo.length());
        assertNotNull(jo.get("ALL"));
    }
    
    /**
     * Sample test case from https://github.com/stleary/JSON-java/issues/531
     * which verifies that no regression in double/BigDecimal support is present.
     */
    @Test
    public void testObjectToBigDecimal() {  
        double value = 1412078745.01074;  
        Reader reader = new StringReader("[{\"value\": " + value + "}]");
        JSONTokener tokener = new JSONTokener(reader);
        JSONArray array = new JSONArray(tokener);
        JSONObject jsonObject = array.getJSONObject(0);

        BigDecimal current = jsonObject.getBigDecimal("value");
        BigDecimal wantedValue = BigDecimal.valueOf(value);

        assertEquals(current, wantedValue);
     }

     @Test
    public void testObjectToBigDecimal2() {
        Reader reader = new StringReader("[");
        JSONTokener tokener = new JSONTokener(reader);
        try {
            JSONArray array = new JSONArray(tokener);
            fail();
        } catch(JSONException e) {
            assertEquals("Expected a ',' or ']' at 1 [character 2 line 1]", e.getMessage());
        }

     }

     @Test
    public void testObjectToBigDecimal3() {
        Reader reader = new StringReader("[1,2,3");
        JSONTokener tokener = new JSONTokener(reader);
        try {
            JSONArray array = new JSONArray(tokener);
            fail();
        } catch(JSONException e) {
            assertEquals("Expected a ',' or ']' at 6 [character 7 line 1]", e.getMessage());
        }

     }

     @Test
    public void testObjectToBigDecimal4() {
        Reader reader = new StringReader("[1,2,3,");
        JSONTokener tokener = new JSONTokener(reader);
        try {
            JSONArray array = new JSONArray(tokener);
            fail();
        } catch(JSONException e) {
            assertEquals("Expected a ',' or ']' at 7 [character 8 line 1]", e.getMessage());
        }

     }
    
    /**
     * Tests the exception portions of populateMap.
     */
    @Test
    public void testExceptionalBean() {
        ExceptionalBean bean = new ExceptionalBean();
        final JSONObject jo = new JSONObject(bean);
        assertEquals("Expected 1 key to be mapped. Instead found: "+jo.keySet().toString(),
                1, jo.length());
        assertTrue(jo.get("closeable") instanceof JSONObject);
        assertTrue(jo.getJSONObject("closeable").has("string"));
    }
    
    @Test(expected=NullPointerException.class)
    public void testPutNullBoolean() {
        // null put key 
        JSONObject jsonObject = new JSONObject("{}");
        jsonObject.put(null, false);
        fail("Expected an exception");
    }
    @Test(expected=NullPointerException.class)
    public void testPutNullCollection() {
        // null put key 
        JSONObject jsonObject = new JSONObject("{}");
        jsonObject.put(null, Collections.emptySet());
        fail("Expected an exception");
    }
    @Test(expected=NullPointerException.class)
    public void testPutNullDouble() {
        // null put key 
        JSONObject jsonObject = new JSONObject("{}");
        jsonObject.put(null, 0.0d);
        fail("Expected an exception");
    }
    @Test(expected=NullPointerException.class)
    public void testPutNullFloat() {
        // null put key 
        JSONObject jsonObject = new JSONObject("{}");
        jsonObject.put(null, 0.0f);
        fail("Expected an exception");
    }
    @Test(expected=NullPointerException.class)
    public void testPutNullInt() {
        // null put key 
        JSONObject jsonObject = new JSONObject("{}");
        jsonObject.put(null, 0);
        fail("Expected an exception");
    }
    @Test(expected=NullPointerException.class)
    public void testPutNullLong() {
        // null put key 
        JSONObject jsonObject = new JSONObject("{}");
        jsonObject.put(null, 0L);
        fail("Expected an exception");
    }
    @Test(expected=NullPointerException.class)
    public void testPutNullMap() {
        // null put key 
        JSONObject jsonObject = new JSONObject("{}");
        jsonObject.put(null, Collections.emptyMap());
        fail("Expected an exception");
    }
    @Test(expected=NullPointerException.class)
    public void testPutNullObject() {
        // null put key 
        JSONObject jsonObject = new JSONObject("{}");
        jsonObject.put(null, new Object());
        fail("Expected an exception");
    }

    @Test
    public void testIssue548ObjectWithEmptyJsonArray() {
        JSONObject jsonObject = new JSONObject("{\"empty_json_array\": []}");
        assertTrue("missing expected key 'empty_json_array'", jsonObject.has("empty_json_array"));
        assertNotNull("'empty_json_array' should be an array", jsonObject.getJSONArray("empty_json_array"));
        assertEquals("'empty_json_array' should have a length of 0", 0, jsonObject.getJSONArray("empty_json_array").length());
    }

    /**
    * Tests if calling JSONObject clear() method actually makes the JSONObject empty
    */
    @Test(expected = JSONException.class)
    public void jsonObjectClearMethodTest() {
        //Adds random stuff to the JSONObject
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("key1", 123);
        jsonObject.put("key2", "456");
        jsonObject.put("key3", new JSONObject());
        jsonObject.clear(); //Clears the JSONObject
        assertTrue("expected jsonObject.length() == 0", jsonObject.length() == 0); //Check if its length is 0
        jsonObject.getInt("key1"); //Should throws org.json.JSONException: JSONObject["asd"] not found
    }
}
