com.xwtec.xwserver.util.json.JSONObject.java Source code

Java tutorial

Introduction

Here is the source code for com.xwtec.xwserver.util.json.JSONObject.java

Source

/*
 * Copyright 2002-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.xwtec.xwserver.util.json;

import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.ezmorph.Morpher;
import net.sf.ezmorph.array.ObjectArrayMorpher;
import net.sf.ezmorph.bean.BeanMorpher;
import net.sf.ezmorph.object.IdentityObjectMorpher;

import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.map.ListOrderedMap;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.xwtec.xwserver.util.json.processors.JsonBeanProcessor;
import com.xwtec.xwserver.util.json.processors.JsonValueProcessor;
import com.xwtec.xwserver.util.json.processors.JsonVerifier;
import com.xwtec.xwserver.util.json.processors.PropertyNameProcessor;
import com.xwtec.xwserver.util.json.regexp.RegexpUtils;
import com.xwtec.xwserver.util.json.util.CycleDetectionStrategy;
import com.xwtec.xwserver.util.json.util.JSONTokener;
import com.xwtec.xwserver.util.json.util.JSONUtils;
import com.xwtec.xwserver.util.json.util.PropertyFilter;
import com.xwtec.xwserver.util.json.util.PropertySetStrategy;

/**
 * A JSONObject is an unordered collection of name/value pairs. Its external
 * form is a string wrapped in curly braces with colons between the names and
 * values, and commas between the values and names. The internal form is an
 * object having <code>get</code> and <code>opt</code> methods for accessing
 * the values by name, and <code>put</code> methods for adding or replacing
 * values by name. The values can be any of these types: <code>Boolean</code>,
 * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>,
 * <code>String</code>, or the <code>JSONNull</code> object. A JSONObject
 * constructor can be used to convert an external form JSON text into an
 * internal form whose values can be retrieved with the <code>get</code> and
 * <code>opt</code> methods, or to convert values into a JSON text using the
 * <code>element</code> and <code>toString</code> methods. A
 * <code>get</code> method returns a value if one can be found, and throws an
 * exception if one cannot be found. An <code>opt</code> method returns a
 * default value instead of throwing an exception, and so is useful for
 * obtaining optional values.
 * <p>
 * The generic <code>get()</code> and <code>opt()</code> methods return an
 * object, which you can cast or query for type. There are also typed
 * <code>get</code> and <code>opt</code> methods that do type checking and
 * type coercion for you.
 * <p>
 * The <code>put</code> methods adds values to an object. For example,
 *
 * <pre>
 *     myString = new JSONObject().put("JSON", "Hello, World!").toString();</pre>
 *
 * produces the string <code>{"JSON": "Hello, World"}</code>.
 * <p>
 * The texts produced by the <code>toString</code> methods strictly conform to
 * the JSON syntax rules. The constructors are more forgiving in the texts they
 * will accept:
 * <ul>
 * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
 * before the closing brace.</li>
 * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single quote)</small>.</li>
 * <li>Strings do not need to be quoted at all if they do not begin with a
 * quote or single quote, and if they do not contain leading or trailing spaces,
 * and if they do not contain any of these characters:
 * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and
 * if they are not the reserved words <code>true</code>, <code>false</code>,
 * or <code>null</code>.</li>
 * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as
 * by <code>:</code>.</li>
 * <li>Values can be followed by <code>;</code> <small>(semicolon)</small>
 * as well as by <code>,</code> <small>(comma)</small>.</li>
 * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or
 * <code>0x-</code> <small>(hex)</small> prefix.</li>
 * <li>Comments written in the slashshlash, slashstar, and hash conventions
 * will be ignored.</li>
 * </ul>
 *
 * @author JSON.org
 */
@SuppressWarnings({ "unchecked", "serial" })
public final class JSONObject extends AbstractJSON implements JSON, Map, Comparable {

    private static final Log log = LogFactory.getLog(JSONObject.class);

    /**
     * Creates a JSONObject.<br>
     * Inspects the object type to call the correct JSONObject factory method.
     * Accepts JSON formatted strings, Maps, DynaBeans and JavaBeans.
     *
     * @param object
     * @throws JSONException if the object can not be converted to a proper
     *         JSONObject.
     */

    public static JSONObject fromObject(Object object) {
        return fromObject(object, new JsonConfig());
    }

    /**
     * Creates a JSONObject.<br>
     * Inspects the object type to call the correct JSONObject factory method.
     * Accepts JSON formatted strings, Maps, DynaBeans and JavaBeans.
     *
     * @param object
     * @throws JSONException if the object can not be converted to a proper
     *         JSONObject.
     */
    public static JSONObject fromObject(Object object, JsonConfig jsonConfig) {
        if (object == null || JSONUtils.isNull(object)) {
            return new JSONObject(true);
        } else if (object instanceof JSONObject) {
            return _fromJSONObject((JSONObject) object, jsonConfig);
        } else if (object instanceof DynaBean) {
            return _fromDynaBean((DynaBean) object, jsonConfig);
        } else if (object instanceof JSONTokener) {
            return _fromJSONTokener((JSONTokener) object, jsonConfig);
        } else if (object instanceof JSONString) {
            return _fromJSONString((JSONString) object, jsonConfig);
        } else if (object instanceof Map) {
            return _fromMap((Map) object, jsonConfig);
        } else if (object instanceof String) {
            return _fromString((String) object, jsonConfig);
        } else if (JSONUtils.isNumber(object) || JSONUtils.isBoolean(object) || JSONUtils.isString(object)) {
            return new JSONObject();
        } else if (JSONUtils.isArray(object)) {
            throw new JSONException("'object' is an array. Use JSONArray instead");
        } else {
            return _fromBean(object, jsonConfig);
        }
    }

    /**
     * Creates a JSONDynaBean from a JSONObject.
     */
    public static Object toBean(JSONObject jsonObject) {
        if (jsonObject == null || jsonObject.isNullObject()) {
            return null;
        }

        DynaBean dynaBean = null;

        JsonConfig jsonConfig = new JsonConfig();
        Map props = JSONUtils.getProperties(jsonObject);
        dynaBean = JSONUtils.newDynaBean(jsonObject, jsonConfig);
        for (Iterator entries = jsonObject.names(jsonConfig).iterator(); entries.hasNext();) {
            String name = (String) entries.next();
            String key = JSONUtils.convertToJavaIdentifier(name, jsonConfig);
            Class type = (Class) props.get(name);
            Object value = jsonObject.get(name);
            try {
                if (!JSONUtils.isNull(value)) {
                    if (value instanceof JSONArray) {
                        dynaBean.set(key, JSONArray.toCollection((JSONArray) value));
                    } else if (String.class.isAssignableFrom(type) || Boolean.class.isAssignableFrom(type)
                            || JSONUtils.isNumber(type) || Character.class.isAssignableFrom(type)
                            || JSONFunction.class.isAssignableFrom(type)) {
                        dynaBean.set(key, value);
                    } else {
                        dynaBean.set(key, toBean((JSONObject) value));
                    }
                } else {
                    if (type.isPrimitive()) {
                        // assume assigned default value
                        log.warn("Tried to assign null value to " + key + ":" + type.getName());
                        dynaBean.set(key, JSONUtils.getMorpherRegistry().morph(type, null));
                    } else {
                        dynaBean.set(key, null);
                    }
                }
            } catch (JSONException jsone) {
                throw jsone;
            } catch (Exception e) {
                throw new JSONException("Error while setting property=" + name + " type" + type, e);
            }
        }

        return dynaBean;
    }

    /**
     * Creates a bean from a JSONObject, with a specific target class.<br>
     */
    public static Object toBean(JSONObject jsonObject, Class beanClass) {
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.setRootClass(beanClass);
        return toBean(jsonObject, jsonConfig);
    }

    /**
     * Creates a bean from a JSONObject, with a specific target class.<br>
     * If beanClass is null, this method will return a graph of DynaBeans. Any
     * attribute that is a JSONObject and matches a key in the classMap will be
     * converted to that target class.<br>
     * The classMap has the following conventions:
     * <ul>
     * <li>Every key must be an String.</li>
     * <li>Every value must be a Class.</li>
     * <li>A key may be a regular expression.</li>
     * </ul>
     */
    public static Object toBean(JSONObject jsonObject, Class beanClass, Map classMap) {
        JsonConfig jsonConfig = new JsonConfig();
        jsonConfig.setRootClass(beanClass);
        jsonConfig.setClassMap(classMap);
        return toBean(jsonObject, jsonConfig);
    }

    /**
     * Creates a bean from a JSONObject, with the specific configuration.
     */
    public static Object toBean(JSONObject jsonObject, JsonConfig jsonConfig) {
        if (jsonObject == null || jsonObject.isNullObject()) {
            return null;
        }

        Class beanClass = jsonConfig.getRootClass();
        Map classMap = jsonConfig.getClassMap();

        if (beanClass == null) {
            return toBean(jsonObject);
        }
        if (classMap == null) {
            classMap = Collections.EMPTY_MAP;
        }

        Object bean = null;
        try {
            if (beanClass.isInterface()) {
                if (!Map.class.isAssignableFrom(beanClass)) {
                    throw new JSONException("beanClass is an interface. " + beanClass);
                } else {
                    bean = new HashMap();
                }
            } else {
                bean = jsonConfig.getNewBeanInstanceStrategy().newInstance(beanClass, jsonObject);
            }
        } catch (JSONException jsone) {
            throw jsone;
        } catch (Exception e) {
            throw new JSONException(e);
        }

        Map props = JSONUtils.getProperties(jsonObject);
        PropertyFilter javaPropertyFilter = jsonConfig.getJavaPropertyFilter();
        for (Iterator entries = jsonObject.names(jsonConfig).iterator(); entries.hasNext();) {
            String name = (String) entries.next();
            Class type = (Class) props.get(name);
            Object value = jsonObject.get(name);
            if (javaPropertyFilter != null && javaPropertyFilter.apply(bean, name, value)) {
                continue;
            }
            String key = Map.class.isAssignableFrom(beanClass)
                    && jsonConfig.isSkipJavaIdentifierTransformationInMapKeys() ? name
                            : JSONUtils.convertToJavaIdentifier(name, jsonConfig);
            PropertyNameProcessor propertyNameProcessor = jsonConfig.findJavaPropertyNameProcessor(beanClass);
            if (propertyNameProcessor != null) {
                key = propertyNameProcessor.processPropertyName(beanClass, key);
            }
            try {
                if (Map.class.isAssignableFrom(beanClass)) {
                    // no type info available for conversion
                    if (JSONUtils.isNull(value)) {
                        setProperty(bean, key, value, jsonConfig);
                    } else if (value instanceof JSONArray) {
                        setProperty(bean, key, convertPropertyValueToCollection(key, value, jsonConfig, name,
                                classMap, List.class), jsonConfig);
                    } else if (String.class.isAssignableFrom(type) || JSONUtils.isBoolean(type)
                            || JSONUtils.isNumber(type) || JSONUtils.isString(type)
                            || JSONFunction.class.isAssignableFrom(type)) {
                        if (jsonConfig.isHandleJettisonEmptyElement() && "".equals(value)) {
                            setProperty(bean, key, null, jsonConfig);
                        } else {
                            setProperty(bean, key, value, jsonConfig);
                        }
                    } else {
                        Class targetClass = findTargetClass(key, classMap);
                        targetClass = targetClass == null ? findTargetClass(name, classMap) : targetClass;
                        JsonConfig jsc = jsonConfig.copy();
                        jsc.setRootClass(targetClass);
                        jsc.setClassMap(classMap);
                        if (targetClass != null) {
                            setProperty(bean, key, toBean((JSONObject) value, jsc), jsonConfig);
                        } else {
                            setProperty(bean, key, toBean((JSONObject) value), jsonConfig);
                        }
                    }
                } else {
                    PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(bean, key);
                    if (pd != null && pd.getWriteMethod() == null) {
                        log.info("Property '" + key + "' of " + bean.getClass() + " has no write method. SKIPPED.");
                        continue;
                    }

                    if (pd != null) {
                        Class targetType = pd.getPropertyType();
                        if (!JSONUtils.isNull(value)) {
                            if (value instanceof JSONArray) {
                                if (List.class.isAssignableFrom(pd.getPropertyType())) {
                                    setProperty(bean, key, convertPropertyValueToCollection(key, value, jsonConfig,
                                            name, classMap, pd.getPropertyType()), jsonConfig);
                                } else if (Set.class.isAssignableFrom(pd.getPropertyType())) {
                                    setProperty(bean, key, convertPropertyValueToCollection(key, value, jsonConfig,
                                            name, classMap, pd.getPropertyType()), jsonConfig);
                                } else {
                                    setProperty(bean, key, convertPropertyValueToArray(key, value, targetType,
                                            jsonConfig, classMap), jsonConfig);
                                }
                            } else if (String.class.isAssignableFrom(type) || JSONUtils.isBoolean(type)
                                    || JSONUtils.isNumber(type) || JSONUtils.isString(type)
                                    || JSONFunction.class.isAssignableFrom(type)) {
                                if (pd != null) {
                                    if (jsonConfig.isHandleJettisonEmptyElement() && "".equals(value)) {
                                        setProperty(bean, key, null, jsonConfig);
                                    } else if (!targetType.isInstance(value)) {
                                        setProperty(bean, key, morphPropertyValue(key, value, type, targetType),
                                                jsonConfig);
                                    } else {
                                        setProperty(bean, key, value, jsonConfig);
                                    }
                                } else if (beanClass == null || bean instanceof Map) {
                                    setProperty(bean, key, value, jsonConfig);
                                } else {
                                    log.warn("Tried to assign property " + key + ":" + type.getName()
                                            + " to bean of class " + bean.getClass().getName());
                                }
                            } else {
                                if (jsonConfig.isHandleJettisonSingleElementArray()) {
                                    JSONArray array = new JSONArray().element(value, jsonConfig);
                                    Class newTargetClass = findTargetClass(key, classMap);
                                    newTargetClass = newTargetClass == null ? findTargetClass(name, classMap)
                                            : newTargetClass;
                                    JsonConfig jsc = jsonConfig.copy();
                                    jsc.setRootClass(newTargetClass);
                                    jsc.setClassMap(classMap);
                                    if (targetType.isArray()) {
                                        setProperty(bean, key, JSONArray.toArray(array, jsc), jsonConfig);
                                    } else if (JSONArray.class.isAssignableFrom(targetType)) {
                                        setProperty(bean, key, array, jsonConfig);
                                    } else if (List.class.isAssignableFrom(targetType)
                                            || Set.class.isAssignableFrom(targetType)) {
                                        jsc.setCollectionType(targetType);
                                        setProperty(bean, key, JSONArray.toCollection(array, jsc), jsonConfig);
                                    } else {
                                        setProperty(bean, key, toBean((JSONObject) value, jsc), jsonConfig);
                                    }
                                } else {
                                    if (targetType == Object.class || targetType.isInterface()) {
                                        Class targetTypeCopy = targetType;
                                        targetType = findTargetClass(key, classMap);
                                        targetType = targetType == null ? findTargetClass(name, classMap)
                                                : targetType;
                                        targetType = targetType == null && targetTypeCopy.isInterface()
                                                ? targetTypeCopy
                                                : targetType;
                                    }
                                    JsonConfig jsc = jsonConfig.copy();
                                    jsc.setRootClass(targetType);
                                    jsc.setClassMap(classMap);
                                    setProperty(bean, key, toBean((JSONObject) value, jsc), jsonConfig);
                                }
                            }
                        } else {
                            if (type.isPrimitive()) {
                                // assume assigned default value
                                log.warn("Tried to assign null value to " + key + ":" + type.getName());
                                setProperty(bean, key, JSONUtils.getMorpherRegistry().morph(type, null),
                                        jsonConfig);
                            } else {
                                setProperty(bean, key, null, jsonConfig);
                            }
                        }
                    } else {
                        // pd is null
                        if (!JSONUtils.isNull(value)) {
                            if (value instanceof JSONArray) {
                                setProperty(bean, key, convertPropertyValueToCollection(key, value, jsonConfig,
                                        name, classMap, List.class), jsonConfig);
                            } else if (String.class.isAssignableFrom(type) || JSONUtils.isBoolean(type)
                                    || JSONUtils.isNumber(type) || JSONUtils.isString(type)
                                    || JSONFunction.class.isAssignableFrom(type)) {
                                if (beanClass == null || bean instanceof Map
                                        || jsonConfig.getPropertySetStrategy() != null
                                        || !jsonConfig.isIgnorePublicFields()) {
                                    setProperty(bean, key, value, jsonConfig);
                                } else {
                                    log.warn("Tried to assign property " + key + ":" + type.getName()
                                            + " to bean of class " + bean.getClass().getName());
                                }
                            } else {
                                if (jsonConfig.isHandleJettisonSingleElementArray()) {
                                    Class newTargetClass = findTargetClass(key, classMap);
                                    newTargetClass = newTargetClass == null ? findTargetClass(name, classMap)
                                            : newTargetClass;
                                    JsonConfig jsc = jsonConfig.copy();
                                    jsc.setRootClass(newTargetClass);
                                    jsc.setClassMap(classMap);
                                    setProperty(bean, key, toBean((JSONObject) value, jsc), jsonConfig);
                                } else {
                                    setProperty(bean, key, value, jsonConfig);
                                }
                            }
                        } else {
                            if (type.isPrimitive()) {
                                // assume assigned default value
                                log.warn("Tried to assign null value to " + key + ":" + type.getName());
                                setProperty(bean, key, JSONUtils.getMorpherRegistry().morph(type, null),
                                        jsonConfig);
                            } else {
                                setProperty(bean, key, null, jsonConfig);
                            }
                        }
                    }
                }
            } catch (JSONException jsone) {
                throw jsone;
            } catch (Exception e) {
                throw new JSONException("Error while setting property=" + name + " type " + type, e);
            }
        }

        return bean;
    }

    /**
     * Creates a bean from a JSONObject, with the specific configuration.
     */
    public static Object toBean(JSONObject jsonObject, Object root, JsonConfig jsonConfig) {
        if (jsonObject == null || jsonObject.isNullObject() || root == null) {
            return root;
        }

        Class rootClass = root.getClass();
        if (rootClass.isInterface()) {
            throw new JSONException("Root bean is an interface. " + rootClass);
        }

        Map classMap = jsonConfig.getClassMap();
        if (classMap == null) {
            classMap = Collections.EMPTY_MAP;
        }

        Map props = JSONUtils.getProperties(jsonObject);
        PropertyFilter javaPropertyFilter = jsonConfig.getJavaPropertyFilter();
        for (Iterator entries = jsonObject.names(jsonConfig).iterator(); entries.hasNext();) {
            String name = (String) entries.next();
            Class type = (Class) props.get(name);
            Object value = jsonObject.get(name);
            if (javaPropertyFilter != null && javaPropertyFilter.apply(root, name, value)) {
                continue;
            }
            String key = JSONUtils.convertToJavaIdentifier(name, jsonConfig);
            try {
                PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(root, key);
                if (pd != null && pd.getWriteMethod() == null) {
                    log.info("Property '" + key + "' of " + root.getClass() + " has no write method. SKIPPED.");
                    continue;
                }

                if (!JSONUtils.isNull(value)) {
                    if (value instanceof JSONArray) {
                        if (pd == null || List.class.isAssignableFrom(pd.getPropertyType())) {
                            Class targetClass = findTargetClass(key, classMap);
                            targetClass = targetClass == null ? findTargetClass(name, classMap) : targetClass;
                            Object newRoot = jsonConfig.getNewBeanInstanceStrategy().newInstance(targetClass, null);
                            List list = JSONArray.toList((JSONArray) value, newRoot, jsonConfig);
                            setProperty(root, key, list, jsonConfig);
                        } else {
                            Class innerType = JSONUtils.getInnerComponentType(pd.getPropertyType());
                            Class targetInnerType = findTargetClass(key, classMap);
                            if (innerType.equals(Object.class) && targetInnerType != null
                                    && !targetInnerType.equals(Object.class)) {
                                innerType = targetInnerType;
                            }
                            Object newRoot = jsonConfig.getNewBeanInstanceStrategy().newInstance(innerType, null);
                            Object array = JSONArray.toArray((JSONArray) value, newRoot, jsonConfig);
                            if (innerType.isPrimitive() || JSONUtils.isNumber(innerType)
                                    || Boolean.class.isAssignableFrom(innerType) || JSONUtils.isString(innerType)) {
                                array = JSONUtils.getMorpherRegistry()
                                        .morph(Array.newInstance(innerType, 0).getClass(), array);
                            } else if (!array.getClass().equals(pd.getPropertyType())) {
                                if (!pd.getPropertyType().equals(Object.class)) {
                                    Morpher morpher = JSONUtils.getMorpherRegistry()
                                            .getMorpherFor(Array.newInstance(innerType, 0).getClass());
                                    if (IdentityObjectMorpher.getInstance().equals(morpher)) {
                                        ObjectArrayMorpher beanMorpher = new ObjectArrayMorpher(
                                                new BeanMorpher(innerType, JSONUtils.getMorpherRegistry()));
                                        JSONUtils.getMorpherRegistry().registerMorpher(beanMorpher);
                                    }
                                    array = JSONUtils.getMorpherRegistry()
                                            .morph(Array.newInstance(innerType, 0).getClass(), array);
                                }
                            }
                            setProperty(root, key, array, jsonConfig);
                        }
                    } else if (String.class.isAssignableFrom(type) || JSONUtils.isBoolean(type)
                            || JSONUtils.isNumber(type) || JSONUtils.isString(type)
                            || JSONFunction.class.isAssignableFrom(type)) {
                        if (pd != null) {
                            if (jsonConfig.isHandleJettisonEmptyElement() && "".equals(value)) {
                                setProperty(root, key, null, jsonConfig);
                            } else if (!pd.getPropertyType().isInstance(value)) {
                                Morpher morpher = JSONUtils.getMorpherRegistry()
                                        .getMorpherFor(pd.getPropertyType());
                                if (IdentityObjectMorpher.getInstance().equals(morpher)) {
                                    log.warn("Can't transform property '" + key + "' from " + type.getName()
                                            + " into " + pd.getPropertyType().getName()
                                            + ". Will register a default BeanMorpher");
                                    JSONUtils.getMorpherRegistry().registerMorpher(
                                            new BeanMorpher(pd.getPropertyType(), JSONUtils.getMorpherRegistry()));
                                }
                                setProperty(root, key,
                                        JSONUtils.getMorpherRegistry().morph(pd.getPropertyType(), value),
                                        jsonConfig);
                            } else {
                                setProperty(root, key, value, jsonConfig);
                            }
                        } else if (root instanceof Map) {
                            setProperty(root, key, value, jsonConfig);
                        } else {
                            log.warn("Tried to assign property " + key + ":" + type.getName() + " to bean of class "
                                    + root.getClass().getName());
                        }
                    } else {
                        if (pd != null) {
                            Class targetClass = pd.getPropertyType();
                            if (jsonConfig.isHandleJettisonSingleElementArray()) {
                                JSONArray array = new JSONArray().element(value, jsonConfig);
                                Class newTargetClass = findTargetClass(key, classMap);
                                newTargetClass = newTargetClass == null ? findTargetClass(name, classMap)
                                        : newTargetClass;
                                Object newRoot = jsonConfig.getNewBeanInstanceStrategy().newInstance(newTargetClass,
                                        null);
                                if (targetClass.isArray()) {
                                    setProperty(root, key, JSONArray.toArray(array, newRoot, jsonConfig),
                                            jsonConfig);
                                } else if (Collection.class.isAssignableFrom(targetClass)) {
                                    setProperty(root, key, JSONArray.toList(array, newRoot, jsonConfig),
                                            jsonConfig);
                                } else if (JSONArray.class.isAssignableFrom(targetClass)) {
                                    setProperty(root, key, array, jsonConfig);
                                } else {
                                    setProperty(root, key, toBean((JSONObject) value, newRoot, jsonConfig),
                                            jsonConfig);
                                }
                            } else {
                                if (targetClass == Object.class) {
                                    targetClass = findTargetClass(key, classMap);
                                    targetClass = targetClass == null ? findTargetClass(name, classMap)
                                            : targetClass;
                                }
                                Object newRoot = jsonConfig.getNewBeanInstanceStrategy().newInstance(targetClass,
                                        null);
                                setProperty(root, key, toBean((JSONObject) value, newRoot, jsonConfig), jsonConfig);
                            }
                        } else if (root instanceof Map) {
                            Class targetClass = findTargetClass(key, classMap);
                            targetClass = targetClass == null ? findTargetClass(name, classMap) : targetClass;
                            Object newRoot = jsonConfig.getNewBeanInstanceStrategy().newInstance(targetClass, null);
                            setProperty(root, key, toBean((JSONObject) value, newRoot, jsonConfig), jsonConfig);
                        } else {
                            log.warn("Tried to assign property " + key + ":" + type.getName() + " to bean of class "
                                    + rootClass.getName());
                        }
                    }
                } else {
                    if (type.isPrimitive()) {
                        // assume assigned default value
                        log.warn("Tried to assign null value to " + key + ":" + type.getName());
                        setProperty(root, key, JSONUtils.getMorpherRegistry().morph(type, null), jsonConfig);
                    } else {
                        setProperty(root, key, null, jsonConfig);
                    }
                }
            } catch (JSONException jsone) {
                throw jsone;
            } catch (Exception e) {
                throw new JSONException("Error while setting property=" + name + " type " + type, e);
            }
        }

        return root;
    }

    /**
     * Creates a JSONObject from a POJO.<br>
     * Supports nested maps, POJOs, and arrays/collections.
     *
     * @param bean An object with POJO conventions
     * @throws JSONException if the bean can not be converted to a proper
     *         JSONObject.
     */
    private static JSONObject _fromBean(Object bean, JsonConfig jsonConfig) {
        if (!addInstance(bean)) {
            try {
                return jsonConfig.getCycleDetectionStrategy().handleRepeatedReferenceAsObject(bean);
            } catch (JSONException jsone) {
                removeInstance(bean);
                fireErrorEvent(jsone, jsonConfig);
                throw jsone;
            } catch (RuntimeException e) {
                removeInstance(bean);
                JSONException jsone = new JSONException(e);
                fireErrorEvent(jsone, jsonConfig);
                throw jsone;
            }
        }
        fireObjectStartEvent(jsonConfig);

        JsonBeanProcessor processor = jsonConfig.findJsonBeanProcessor(bean.getClass());
        if (processor != null) {
            JSONObject json = null;
            try {
                json = processor.processBean(bean, jsonConfig);
                if (json == null) {
                    json = (JSONObject) jsonConfig.findDefaultValueProcessor(bean.getClass())
                            .getDefaultValue(bean.getClass());
                    if (json == null) {
                        json = new JSONObject(true);
                    }
                }
                removeInstance(bean);
                fireObjectEndEvent(jsonConfig);
            } catch (JSONException jsone) {
                removeInstance(bean);
                fireErrorEvent(jsone, jsonConfig);
                throw jsone;
            } catch (RuntimeException e) {
                removeInstance(bean);
                JSONException jsone = new JSONException(e);
                fireErrorEvent(jsone, jsonConfig);
                throw jsone;
            }
            return json;
        }

        Class beanClass = bean.getClass();
        PropertyNameProcessor propertyNameProcessor = jsonConfig.findJsonPropertyNameProcessor(beanClass);
        Collection exclusions = jsonConfig.getMergedExcludes(beanClass);
        JSONObject jsonObject = new JSONObject();
        try {
            PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(bean);
            PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
            for (int i = 0; i < pds.length; i++) {
                boolean bypass = false;
                String key = pds[i].getName();
                if (exclusions.contains(key)) {
                    continue;
                }

                if (jsonConfig.isIgnoreTransientFields() && isTransientField(key, beanClass)) {
                    continue;
                }

                Class type = pds[i].getPropertyType();
                try {
                    pds[i].getReadMethod();
                } catch (Exception e) {
                    // bug 2565295
                    String warning = "Property '" + key + "' of " + beanClass + " has no read method. SKIPPED";
                    fireWarnEvent(warning, jsonConfig);
                    log.info(warning);
                    continue;
                }
                if (pds[i].getReadMethod() != null) {
                    Object value = PropertyUtils.getProperty(bean, key);
                    if (jsonPropertyFilter != null && jsonPropertyFilter.apply(bean, key, value)) {
                        continue;
                    }
                    JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor(beanClass, type, key);
                    if (jsonValueProcessor != null) {
                        value = jsonValueProcessor.processObjectValue(key, value, jsonConfig);
                        bypass = true;
                        if (!JsonVerifier.isValidJsonValue(value)) {
                            throw new JSONException("Value is not a valid JSON value. " + value);
                        }
                    }
                    if (propertyNameProcessor != null) {
                        key = propertyNameProcessor.processPropertyName(beanClass, key);
                    }
                    setValue(jsonObject, key, value, type, jsonConfig, bypass);
                } else {
                    String warning = "Property '" + key + "' of " + beanClass + " has no read method. SKIPPED";
                    fireWarnEvent(warning, jsonConfig);
                    log.info(warning);
                }
            }
            // inspect public fields, this operation may fail under
            // a SecurityManager so we will eat all exceptions
            try {
                if (!jsonConfig.isIgnorePublicFields()) {
                    Field[] fields = beanClass.getFields();
                    for (int i = 0; i < fields.length; i++) {
                        boolean bypass = false;
                        Field field = fields[i];
                        String key = field.getName();
                        if (exclusions.contains(key)) {
                            continue;
                        }

                        if (jsonConfig.isIgnoreTransientFields() && isTransientField(field)) {
                            continue;
                        }

                        Class type = field.getType();
                        Object value = field.get(bean);
                        if (jsonPropertyFilter != null && jsonPropertyFilter.apply(bean, key, value)) {
                            continue;
                        }
                        JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor(beanClass, type,
                                key);
                        if (jsonValueProcessor != null) {
                            value = jsonValueProcessor.processObjectValue(key, value, jsonConfig);
                            bypass = true;
                            if (!JsonVerifier.isValidJsonValue(value)) {
                                throw new JSONException("Value is not a valid JSON value. " + value);
                            }
                        }
                        if (propertyNameProcessor != null) {
                            key = propertyNameProcessor.processPropertyName(beanClass, key);
                        }
                        setValue(jsonObject, key, value, type, jsonConfig, bypass);
                    }
                }
            } catch (Exception e) {
                log.trace("Couldn't read public fields.", e);
            }
        } catch (JSONException jsone) {
            removeInstance(bean);
            fireErrorEvent(jsone, jsonConfig);
            throw jsone;
        } catch (Exception e) {
            removeInstance(bean);
            JSONException jsone = new JSONException(e);
            fireErrorEvent(jsone, jsonConfig);
            throw jsone;
        }

        removeInstance(bean);
        fireObjectEndEvent(jsonConfig);
        return jsonObject;
    }

    private static JSONObject _fromDynaBean(DynaBean bean, JsonConfig jsonConfig) {
        if (bean == null) {
            fireObjectStartEvent(jsonConfig);
            fireObjectEndEvent(jsonConfig);
            return new JSONObject(true);
        }

        if (!addInstance(bean)) {
            try {
                return jsonConfig.getCycleDetectionStrategy().handleRepeatedReferenceAsObject(bean);
            } catch (JSONException jsone) {
                removeInstance(bean);
                fireErrorEvent(jsone, jsonConfig);
                throw jsone;
            } catch (RuntimeException e) {
                removeInstance(bean);
                JSONException jsone = new JSONException(e);
                fireErrorEvent(jsone, jsonConfig);
                throw jsone;
            }
        }
        fireObjectStartEvent(jsonConfig);

        JSONObject jsonObject = new JSONObject();
        try {
            DynaProperty[] props = bean.getDynaClass().getDynaProperties();
            Collection exclusions = jsonConfig.getMergedExcludes();
            PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
            for (int i = 0; i < props.length; i++) {
                boolean bypass = false;
                DynaProperty dynaProperty = props[i];
                String key = dynaProperty.getName();
                if (exclusions.contains(key)) {
                    continue;
                }
                Class type = dynaProperty.getType();
                Object value = bean.get(dynaProperty.getName());
                if (jsonPropertyFilter != null && jsonPropertyFilter.apply(bean, key, value)) {
                    continue;
                }
                JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor(type, key);
                if (jsonValueProcessor != null) {
                    value = jsonValueProcessor.processObjectValue(key, value, jsonConfig);
                    bypass = true;
                    if (!JsonVerifier.isValidJsonValue(value)) {
                        throw new JSONException("Value is not a valid JSON value. " + value);
                    }
                }
                setValue(jsonObject, key, value, type, jsonConfig, bypass);
            }
        } catch (JSONException jsone) {
            removeInstance(bean);
            fireErrorEvent(jsone, jsonConfig);
            throw jsone;
        } catch (RuntimeException e) {
            removeInstance(bean);
            JSONException jsone = new JSONException(e);
            fireErrorEvent(jsone, jsonConfig);
            throw jsone;
        }

        removeInstance(bean);
        fireObjectEndEvent(jsonConfig);
        return jsonObject;
    }

    private static JSONObject _fromJSONObject(JSONObject object, JsonConfig jsonConfig) {
        if (object == null || object.isNullObject()) {
            fireObjectStartEvent(jsonConfig);
            fireObjectEndEvent(jsonConfig);
            return new JSONObject(true);
        }

        if (!addInstance(object)) {
            try {
                return jsonConfig.getCycleDetectionStrategy().handleRepeatedReferenceAsObject(object);
            } catch (JSONException jsone) {
                removeInstance(object);
                fireErrorEvent(jsone, jsonConfig);
                throw jsone;
            } catch (RuntimeException e) {
                removeInstance(object);
                JSONException jsone = new JSONException(e);
                fireErrorEvent(jsone, jsonConfig);
                throw jsone;
            }
        }
        fireObjectStartEvent(jsonConfig);

        JSONArray sa = object.names(jsonConfig);
        Collection exclusions = jsonConfig.getMergedExcludes();
        JSONObject jsonObject = new JSONObject();
        PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
        for (Iterator i = sa.iterator(); i.hasNext();) {
            Object k = i.next();
            if (k == null) {
                throw new JSONException("JSON keys cannot be null.");
            }
            if (!(k instanceof String) && !jsonConfig.isAllowNonStringKeys()) {
                throw new ClassCastException("JSON keys must be strings.");
            }
            String key = String.valueOf(k);
            if ("null".equals(key)) {
                throw new NullPointerException("JSON keys must not be null nor the 'null' string.");
            }
            if (exclusions.contains(key)) {
                continue;
            }
            Object value = object.opt(key);
            if (jsonPropertyFilter != null && jsonPropertyFilter.apply(object, key, value)) {
                continue;
            }
            if (jsonObject.properties.containsKey(key)) {
                jsonObject.accumulate(key, value, jsonConfig);
                firePropertySetEvent(key, value, true, jsonConfig);
            } else {
                jsonObject.setInternal(key, value, jsonConfig);
                firePropertySetEvent(key, value, false, jsonConfig);
            }
        }

        removeInstance(object);
        fireObjectEndEvent(jsonConfig);
        return jsonObject;
    }

    private static JSONObject _fromJSONString(JSONString string, JsonConfig jsonConfig) {
        return _fromJSONTokener(new JSONTokener(string.toJSONString()), jsonConfig);
    }

    private static JSONObject _fromJSONTokener(JSONTokener tokener, JsonConfig jsonConfig) {

        try {
            char c;
            String key;
            Object value;

            if (tokener.matches("null.*")) {
                fireObjectStartEvent(jsonConfig);
                fireObjectEndEvent(jsonConfig);
                return new JSONObject(true);
            }

            if (tokener.nextClean() != '{') {
                throw tokener.syntaxError("A JSONObject text must begin with '{'");
            }
            fireObjectStartEvent(jsonConfig);

            Collection exclusions = jsonConfig.getMergedExcludes();
            PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
            JSONObject jsonObject = new JSONObject();
            for (;;) {
                c = tokener.nextClean();
                switch (c) {
                case 0:
                    throw tokener.syntaxError("A JSONObject text must end with '}'");
                case '}':
                    fireObjectEndEvent(jsonConfig);
                    return jsonObject;
                default:
                    tokener.back();
                    key = tokener.nextValue(jsonConfig).toString();
                }

                /*
                 * The key is followed by ':'. We will also tolerate '=' or '=>'.
                 */

                c = tokener.nextClean();
                if (c == '=') {
                    if (tokener.next() != '>') {
                        tokener.back();
                    }
                } else if (c != ':') {
                    throw tokener.syntaxError("Expected a ':' after a key");
                }

                char peek = tokener.peek();
                boolean quoted = peek == '"' || peek == '\'';
                Object v = tokener.nextValue(jsonConfig);
                if (quoted || !JSONUtils.isFunctionHeader(v)) {
                    if (exclusions.contains(key)) {
                        switch (tokener.nextClean()) {
                        case ';':
                        case ',':
                            if (tokener.nextClean() == '}') {
                                fireObjectEndEvent(jsonConfig);
                                return jsonObject;
                            }
                            tokener.back();
                            break;
                        case '}':
                            fireObjectEndEvent(jsonConfig);
                            return jsonObject;
                        default:
                            throw tokener.syntaxError("Expected a ',' or '}'");
                        }
                        continue;
                    }
                    if (jsonPropertyFilter == null || !jsonPropertyFilter.apply(tokener, key, v)) {
                        if (quoted && v instanceof String
                                && (JSONUtils.mayBeJSON((String) v) || JSONUtils.isFunction(v))) {
                            v = JSONUtils.DOUBLE_QUOTE + v + JSONUtils.DOUBLE_QUOTE;
                        }
                        if (jsonObject.properties.containsKey(key)) {
                            jsonObject.accumulate(key, v, jsonConfig);
                            firePropertySetEvent(key, v, true, jsonConfig);
                        } else {
                            jsonObject.element(key, v, jsonConfig);
                            firePropertySetEvent(key, v, false, jsonConfig);
                        }
                    }
                } else {
                    // read params if any
                    String params = JSONUtils.getFunctionParams((String) v);
                    // read function text
                    int i = 0;
                    StringBuffer sb = new StringBuffer();
                    for (;;) {
                        char ch = tokener.next();
                        if (ch == 0) {
                            break;
                        }
                        if (ch == '{') {
                            i++;
                        }
                        if (ch == '}') {
                            i--;
                        }
                        sb.append(ch);
                        if (i == 0) {
                            break;
                        }
                    }
                    if (i != 0) {
                        throw tokener.syntaxError("Unbalanced '{' or '}' on prop: " + v);
                    }
                    // trim '{' at start and '}' at end
                    String text = sb.toString();
                    text = text.substring(1, text.length() - 1).trim();
                    value = new JSONFunction((params != null) ? StringUtils.split(params, ",") : null, text);
                    if (jsonPropertyFilter == null || !jsonPropertyFilter.apply(tokener, key, value)) {
                        if (jsonObject.properties.containsKey(key)) {
                            jsonObject.accumulate(key, value, jsonConfig);
                            firePropertySetEvent(key, value, true, jsonConfig);
                        } else {
                            jsonObject.element(key, value, jsonConfig);
                            firePropertySetEvent(key, value, false, jsonConfig);
                        }
                    }
                }

                /*
                 * Pairs are separated by ','. We will also tolerate ';'.
                 */

                switch (tokener.nextClean()) {
                case ';':
                case ',':
                    if (tokener.nextClean() == '}') {
                        fireObjectEndEvent(jsonConfig);
                        return jsonObject;
                    }
                    tokener.back();
                    break;
                case '}':
                    fireObjectEndEvent(jsonConfig);
                    return jsonObject;
                default:
                    throw tokener.syntaxError("Expected a ',' or '}'");
                }
            }
        } catch (JSONException jsone) {
            fireErrorEvent(jsone, jsonConfig);
            throw jsone;
        }
    }

    private static JSONObject _fromMap(Map map, JsonConfig jsonConfig) {
        if (map == null) {
            fireObjectStartEvent(jsonConfig);
            fireObjectEndEvent(jsonConfig);
            return new JSONObject(true);
        }

        if (!addInstance(map)) {
            try {
                return jsonConfig.getCycleDetectionStrategy().handleRepeatedReferenceAsObject(map);
            } catch (JSONException jsone) {
                removeInstance(map);
                fireErrorEvent(jsone, jsonConfig);
                throw jsone;
            } catch (RuntimeException e) {
                removeInstance(map);
                JSONException jsone = new JSONException(e);
                fireErrorEvent(jsone, jsonConfig);
                throw jsone;
            }
        }
        fireObjectStartEvent(jsonConfig);

        Collection exclusions = jsonConfig.getMergedExcludes();
        JSONObject jsonObject = new JSONObject();
        PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
        try {
            for (Iterator entries = map.entrySet().iterator(); entries.hasNext();) {
                boolean bypass = false;
                Map.Entry entry = (Map.Entry) entries.next();
                Object k = entry.getKey();
                if (k == null) {
                    throw new JSONException("JSON keys cannot be null.");
                }
                if (!(k instanceof String) && !jsonConfig.isAllowNonStringKeys()) {
                    throw new ClassCastException("JSON keys must be strings.");
                }
                String key = String.valueOf(k);
                if ("null".equals(key)) {
                    throw new NullPointerException("JSON keys must not be null nor the 'null' string.");
                }
                if (exclusions.contains(key)) {
                    continue;
                }
                Object value = entry.getValue();
                if (jsonPropertyFilter != null && jsonPropertyFilter.apply(map, key, value)) {
                    continue;
                }
                if (value != null) {
                    JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor(value.getClass(),
                            key);
                    if (jsonValueProcessor != null) {
                        value = jsonValueProcessor.processObjectValue(key, value, jsonConfig);
                        bypass = true;
                        if (!JsonVerifier.isValidJsonValue(value)) {
                            throw new JSONException("Value is not a valid JSON value. " + value);
                        }
                    }
                    setValue(jsonObject, key, value, value.getClass(), jsonConfig, bypass);
                } else {
                    if (jsonObject.properties.containsKey(key)) {
                        jsonObject.accumulate(key, JSONNull.getInstance());
                        firePropertySetEvent(key, JSONNull.getInstance(), true, jsonConfig);
                    } else {
                        jsonObject.element(key, JSONNull.getInstance());
                        firePropertySetEvent(key, JSONNull.getInstance(), false, jsonConfig);
                    }
                }
            }
        } catch (JSONException jsone) {
            removeInstance(map);
            fireErrorEvent(jsone, jsonConfig);
            throw jsone;
        } catch (RuntimeException e) {
            removeInstance(map);
            JSONException jsone = new JSONException(e);
            fireErrorEvent(jsone, jsonConfig);
            throw jsone;
        }

        removeInstance(map);
        fireObjectEndEvent(jsonConfig);
        return jsonObject;
    }

    private static JSONObject _fromString(String str, JsonConfig jsonConfig) {
        if (str == null || "null".equals(str)) {
            fireObjectStartEvent(jsonConfig);
            fireObjectEndEvent(jsonConfig);
            return new JSONObject(true);
        }
        return _fromJSONTokener(new JSONTokener(str), jsonConfig);
    }

    private static Object convertPropertyValueToArray(String key, Object value, Class targetType,
            JsonConfig jsonConfig, Map classMap) {
        Class innerType = JSONUtils.getInnerComponentType(targetType);
        Class targetInnerType = findTargetClass(key, classMap);
        if (innerType.equals(Object.class) && targetInnerType != null && !targetInnerType.equals(Object.class)) {
            innerType = targetInnerType;
        }
        JsonConfig jsc = jsonConfig.copy();
        jsc.setRootClass(innerType);
        jsc.setClassMap(classMap);
        Object array = JSONArray.toArray((JSONArray) value, jsc);
        if (innerType.isPrimitive() || JSONUtils.isNumber(innerType) || Boolean.class.isAssignableFrom(innerType)
                || JSONUtils.isString(innerType)) {
            array = JSONUtils.getMorpherRegistry().morph(Array.newInstance(innerType, 0).getClass(), array);
        } else if (!array.getClass().equals(targetType)) {
            if (!targetType.equals(Object.class)) {
                Morpher morpher = JSONUtils.getMorpherRegistry()
                        .getMorpherFor(Array.newInstance(innerType, 0).getClass());
                if (IdentityObjectMorpher.getInstance().equals(morpher)) {
                    ObjectArrayMorpher beanMorpher = new ObjectArrayMorpher(
                            new BeanMorpher(innerType, JSONUtils.getMorpherRegistry()));
                    JSONUtils.getMorpherRegistry().registerMorpher(beanMorpher);
                }
                array = JSONUtils.getMorpherRegistry().morph(Array.newInstance(innerType, 0).getClass(), array);
            }
        }
        return array;
    }

    /**
     * modified by: houxu@xwtec.cn
     * ??:The method convertPropertyValueToList(String, Object, JsonConfig, String, Map) from the type JSONObject is never used locally
     */
    //   private static List convertPropertyValueToList( String key, Object value, JsonConfig jsonConfig,String name, Map classMap ) {
    //      
    //      Class targetClass = findTargetClass( key, classMap );
    //      targetClass = targetClass == null ? findTargetClass( name, classMap ) : targetClass;
    //      JsonConfig jsc = jsonConfig.copy();
    //      jsc.setRootClass( targetClass );
    //      jsc.setClassMap( classMap );
    //      List list = (List) JSONArray.toCollection( (JSONArray) value, jsc );
    //      
    //      return list;
    //   }

    private static Collection convertPropertyValueToCollection(String key, Object value, JsonConfig jsonConfig,
            String name, Map classMap, Class collectionType) {
        Class targetClass = findTargetClass(key, classMap);
        targetClass = targetClass == null ? findTargetClass(name, classMap) : targetClass;
        JsonConfig jsc = jsonConfig.copy();
        jsc.setRootClass(targetClass);
        jsc.setClassMap(classMap);
        jsc.setCollectionType(collectionType);
        return JSONArray.toCollection((JSONArray) value, jsc);
    }

    /**
     * Locates a Class associated to a specifi key.<br>
     * The key may be a regexp.
     */
    private static Class findTargetClass(String key, Map classMap) {
        // try get first
        Class targetClass = (Class) classMap.get(key);
        if (targetClass == null) {
            // try with regexp
            // this will hit performance as it must iterate over all the keys
            // and create a RegexpMatcher for each key
            for (Iterator i = classMap.entrySet().iterator(); i.hasNext();) {
                Map.Entry entry = (Map.Entry) i.next();
                if (RegexpUtils.getMatcher((String) entry.getKey()).matches(key)) {
                    targetClass = (Class) entry.getValue();
                    break;
                }
            }
        }

        return targetClass;
    }

    private static boolean isTransientField(String name, Class beanClass) {
        try {
            return isTransientField(beanClass.getDeclaredField(name));
        } catch (Exception e) {
            // swallow exception
        }
        return false;
    }

    private static boolean isTransientField(Field field) {
        return (field.getModifiers() & Modifier.TRANSIENT) == Modifier.TRANSIENT;
    }

    private static Object morphPropertyValue(String key, Object value, Class type, Class targetType) {
        Morpher morpher = JSONUtils.getMorpherRegistry().getMorpherFor(targetType);
        if (IdentityObjectMorpher.getInstance().equals(morpher)) {
            log.warn("Can't transform property '" + key + "' from " + type.getName() + " into "
                    + targetType.getName() + ". Will register a default BeanMorpher");
            JSONUtils.getMorpherRegistry()
                    .registerMorpher(new BeanMorpher(targetType, JSONUtils.getMorpherRegistry()));
        }
        value = JSONUtils.getMorpherRegistry().morph(targetType, value);
        return value;
    }

    /**
     * Sets a property on the target bean.<br>
     * Bean may be a Map or a POJO.
     */
    private static void setProperty(Object bean, String key, Object value, JsonConfig jsonConfig) throws Exception {
        PropertySetStrategy propertySetStrategy = jsonConfig.getPropertySetStrategy() != null
                ? jsonConfig.getPropertySetStrategy()
                : PropertySetStrategy.DEFAULT;
        propertySetStrategy.setProperty(bean, key, value, jsonConfig);
    }

    private static void setValue(JSONObject jsonObject, String key, Object value, Class type, JsonConfig jsonConfig,
            boolean bypass) {
        boolean accumulated = false;
        if (value == null) {
            value = jsonConfig.findDefaultValueProcessor(type).getDefaultValue(type);
            if (!JsonVerifier.isValidJsonValue(value)) {
                throw new JSONException("Value is not a valid JSON value. " + value);
            }
        }
        if (jsonObject.properties.containsKey(key)) {
            if (String.class.isAssignableFrom(type)) {
                Object o = jsonObject.opt(key);
                if (o instanceof JSONArray) {
                    ((JSONArray) o).addString((String) value);
                } else {
                    jsonObject.properties.put(key, new JSONArray().element(o).addString((String) value));
                }
            } else {
                jsonObject.accumulate(key, value, jsonConfig);
            }
            accumulated = true;
        } else {
            if (bypass || String.class.isAssignableFrom(type)) {
                jsonObject.properties.put(key, value);
            } else {
                jsonObject.setInternal(key, value, jsonConfig);
            }
        }

        value = jsonObject.opt(key);
        if (accumulated) {
            JSONArray array = (JSONArray) value;
            value = array.get(array.size() - 1);
        }
        firePropertySetEvent(key, value, accumulated, jsonConfig);
    }

    // ------------------------------------------------------

    /** identifies this object as null */
    private boolean nullObject;

    /**
     * The Map where the JSONObject's properties are kept.
     */
    private Map properties;

    /**
     * Construct an empty JSONObject.
     */
    public JSONObject() {
        this.properties = new ListOrderedMap();
    }

    /**
     * Creates a JSONObject that is null.
     */
    public JSONObject(boolean isNull) {
        this();
        this.nullObject = isNull;
    }

    /**
     * Accumulate values under a key. It is similar to the element method except
     * that if there is already an object stored under the key then a JSONArray
     * is stored under the key to hold all of the accumulated values. If there is
     * already a JSONArray, then the new value is appended to it. In contrast,
     * the replace method replaces the previous value.
     *
     * @param key A key string.
     * @param value An object to be accumulated under the key.
     * @return this.
     * @throws JSONException If the value is an invalid number or if the key is
     *         null.
     */
    public JSONObject accumulate(String key, boolean value) {
        return _accumulate(key, value ? Boolean.TRUE : Boolean.FALSE, new JsonConfig());
    }

    /**
     * Accumulate values under a key. It is similar to the element method except
     * that if there is already an object stored under the key then a JSONArray
     * is stored under the key to hold all of the accumulated values. If there is
     * already a JSONArray, then the new value is appended to it. In contrast,
     * the replace method replaces the previous value.
     *
     * @param key A key string.
     * @param value An object to be accumulated under the key.
     * @return this.
     * @throws JSONException If the value is an invalid number or if the key is
     *         null.
     */
    public JSONObject accumulate(String key, double value) {
        return _accumulate(key, new Double(value), new JsonConfig());
    }

    /**
     * Accumulate values under a key. It is similar to the element method except
     * that if there is already an object stored under the key then a JSONArray
     * is stored under the key to hold all of the accumulated values. If there is
     * already a JSONArray, then the new value is appended to it. In contrast,
     * the replace method replaces the previous value.
     *
     * @param key A key string.
     * @param value An object to be accumulated under the key.
     * @return this.
     * @throws JSONException If the value is an invalid number or if the key is
     *         null.
     */
    public JSONObject accumulate(String key, int value) {
        return _accumulate(key, new Integer(value), new JsonConfig());
    }

    /**
     * Accumulate values under a key. It is similar to the element method except
     * that if there is already an object stored under the key then a JSONArray
     * is stored under the key to hold all of the accumulated values. If there is
     * already a JSONArray, then the new value is appended to it. In contrast,
     * the replace method replaces the previous value.
     *
     * @param key A key string.
     * @param value An object to be accumulated under the key.
     * @return this.
     * @throws JSONException If the value is an invalid number or if the key is
     *         null.
     */
    public JSONObject accumulate(String key, long value) {
        return _accumulate(key, new Long(value), new JsonConfig());
    }

    /**
     * Accumulate values under a key. It is similar to the element method except
     * that if there is already an object stored under the key then a JSONArray
     * is stored under the key to hold all of the accumulated values. If there is
     * already a JSONArray, then the new value is appended to it. In contrast,
     * the replace method replaces the previous value.
     *
     * @param key A key string.
     * @param value An object to be accumulated under the key.
     * @return this.
     * @throws JSONException If the value is an invalid number or if the key is
     *         null.
     */
    public JSONObject accumulate(String key, Object value) {
        return _accumulate(key, value, new JsonConfig());
    }

    /**
     * Accumulate values under a key. It is similar to the element method except
     * that if there is already an object stored under the key then a JSONArray
     * is stored under the key to hold all of the accumulated values. If there is
     * already a JSONArray, then the new value is appended to it. In contrast,
     * the replace method replaces the previous value.
     *
     * @param key A key string.
     * @param value An object to be accumulated under the key.
     * @return this.
     * @throws JSONException If the value is an invalid number or if the key is
     *         null.
     */
    public JSONObject accumulate(String key, Object value, JsonConfig jsonConfig) {
        return _accumulate(key, value, jsonConfig);
    }

    public void accumulateAll(Map map) {
        accumulateAll(map, new JsonConfig());
    }

    public void accumulateAll(Map map, JsonConfig jsonConfig) {
        if (map instanceof JSONObject) {
            for (Iterator entries = map.entrySet().iterator(); entries.hasNext();) {
                Map.Entry entry = (Map.Entry) entries.next();
                String key = (String) entry.getKey();
                Object value = entry.getValue();
                accumulate(key, value, jsonConfig);
            }
        } else {
            for (Iterator entries = map.entrySet().iterator(); entries.hasNext();) {
                Map.Entry entry = (Map.Entry) entries.next();
                String key = String.valueOf(entry.getKey());
                Object value = entry.getValue();
                accumulate(key, value, jsonConfig);
            }
        }
    }

    public void clear() {
        properties.clear();
    }

    public int compareTo(Object obj) {
        if (obj != null && (obj instanceof JSONObject)) {
            JSONObject other = (JSONObject) obj;
            int size1 = size();
            int size2 = other.size();
            if (size1 < size2) {
                return -1;
            } else if (size1 > size2) {
                return 1;
            } else if (this.equals(other)) {
                return 0;
            }
        }
        return -1;
    }

    public boolean containsKey(Object key) {
        return properties.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return containsValue(value, new JsonConfig());
    }

    public boolean containsValue(Object value, JsonConfig jsonConfig) {
        try {
            value = processValue(value, jsonConfig);
        } catch (JSONException e) {
            return false;
        }
        return properties.containsValue(value);
    }

    /**
     * Remove a name and its value, if present.
     *
     * @param key A key string.
     * @return this.
     */
    public JSONObject discard(String key) {
        verifyIsNull();
        this.properties.remove(key);
        return this;
    }

    /**
     * Put a key/boolean pair in the JSONObject.
     *
     * @param key A key string.
     * @param value A boolean which is the value.
     * @return this.
     * @throws JSONException If the key is null.
     */
    public JSONObject element(String key, boolean value) {
        verifyIsNull();
        return element(key, value ? Boolean.TRUE : Boolean.FALSE);
    }

    /**
     * Put a key/value pair in the JSONObject, where the value will be a
     * JSONArray which is produced from a Collection.
     *
     * @param key A key string.
     * @param value A Collection value.
     * @return this.
     * @throws JSONException
     */
    public JSONObject element(String key, Collection value) {
        return element(key, value, new JsonConfig());
    }

    /**
     * Put a key/value pair in the JSONObject, where the value will be a
     * JSONArray which is produced from a Collection.
     *
     * @param key A key string.
     * @param value A Collection value.
     * @return this.
     * @throws JSONException
     */
    public JSONObject element(String key, Collection value, JsonConfig jsonConfig) {
        verifyIsNull();
        if (!(value instanceof JSONArray)) {
            value = JSONArray.fromObject(value, jsonConfig);
        }
        return setInternal(key, value, jsonConfig);
    }

    /**
     * Put a key/double pair in the JSONObject.
     *
     * @param key A key string.
     * @param value A double which is the value.
     * @return this.
     * @throws JSONException If the key is null or if the number is invalid.
     */
    public JSONObject element(String key, double value) {
        verifyIsNull();
        Double d = new Double(value);
        JSONUtils.testValidity(d);
        return element(key, d);
    }

    /**
     * Put a key/int pair in the JSONObject.
     *
     * @param key A key string.
     * @param value An int which is the value.
     * @return this.
     * @throws JSONException If the key is null.
     */
    public JSONObject element(String key, int value) {
        verifyIsNull();
        return element(key, new Integer(value));
    }

    /**
     * Put a key/long pair in the JSONObject.
     *
     * @param key A key string.
     * @param value A long which is the value.
     * @return this.
     * @throws JSONException If the key is null.
     */
    public JSONObject element(String key, long value) {
        verifyIsNull();
        return element(key, new Long(value));
    }

    /**
     * Put a key/value pair in the JSONObject, where the value will be a
     * JSONObject which is produced from a Map.
     *
     * @param key A key string.
     * @param value A Map value.
     * @return this.
     * @throws JSONException
     */
    public JSONObject element(String key, Map value) {
        return element(key, value, new JsonConfig());
    }

    /**
     * Put a key/value pair in the JSONObject, where the value will be a
     * JSONObject which is produced from a Map.
     *
     * @param key A key string.
     * @param value A Map value.
     * @return this.
     * @throws JSONException
     */
    public JSONObject element(String key, Map value, JsonConfig jsonConfig) {
        verifyIsNull();
        if (value instanceof JSONObject) {
            return setInternal(key, value, jsonConfig);
        } else {
            return element(key, JSONObject.fromObject(value, jsonConfig), jsonConfig);
        }
    }

    /**
     * Put a key/value pair in the JSONObject. If the value is null, then the key
     * will be removed from the JSONObject if it is present.<br>
     * If there is a previous value assigned to the key, it will call accumulate.
     *
     * @param key A key string.
     * @param value An object which is the value. It should be of one of these
     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
     *        String, or the JSONNull object.
     * @return this.
     * @throws JSONException If the value is non-finite number or if the key is
     *         null.
     */
    public JSONObject element(String key, Object value) {
        return element(key, value, new JsonConfig());
    }

    /**
     * Put a key/value pair in the JSONObject. If the value is null, then the key
     * will be removed from the JSONObject if it is present.<br>
     * If there is a previous value assigned to the key, it will call accumulate.
     *
     * @param key A key string.
     * @param value An object which is the value. It should be of one of these
     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
     *        String, or the JSONNull object.
     * @return this.
     * @throws JSONException If the value is non-finite number or if the key is
     *         null.
     */
    public JSONObject element(String key, Object value, JsonConfig jsonConfig) {
        verifyIsNull();
        if (key == null) {
            throw new JSONException("Null key.");
        }
        /** modified by houxu 2013-09-02
         * ?JSONNull?
         * null,?
        if( value != null ){
           value = processValue( key, value, jsonConfig );
           _setInternal( key, value, jsonConfig );
        }else{
           remove( key );
        }
        */
        if ((value instanceof JSONNull) || value == null)
            value = "";

        value = processValue(key, value, jsonConfig);
        _setInternal(key, value, jsonConfig);

        return this;
    }

    /**
     * Put a key/value pair in the JSONObject, but only if the key and the value
     * are both non-null.
     *
     * @param key A key string.
     * @param value An object which is the value. It should be of one of these
     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
     *        String, or the JSONNull object.
     * @return this.
     * @throws JSONException If the value is a non-finite number.
     */
    public JSONObject elementOpt(String key, Object value) {
        return elementOpt(key, value, new JsonConfig());
    }

    /**
     * Put a key/value pair in the JSONObject, but only if the key and the value
     * are both non-null.
     *
     * @param key A key string.
     * @param value An object which is the value. It should be of one of these
     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
     *        String, or the JSONNull object.
     * @return this.
     * @throws JSONException If the value is a non-finite number.
     */
    public JSONObject elementOpt(String key, Object value, JsonConfig jsonConfig) {
        verifyIsNull();
        if (key != null && value != null) {
            element(key, value, jsonConfig);
        }
        return this;
    }

    public Set entrySet() {
        return Collections.unmodifiableSet(properties.entrySet());
    }

    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj == null) {
            return false;
        }

        if (!(obj instanceof JSONObject)) {
            return false;
        }

        JSONObject other = (JSONObject) obj;

        if (isNullObject()) {
            if (other.isNullObject()) {
                return true;
            } else {
                return false;
            }
        } else {
            if (other.isNullObject()) {
                return false;
            }
        }

        if (other.size() != size()) {
            return false;
        }

        for (Iterator keys = properties.keySet().iterator(); keys.hasNext();) {
            String key = (String) keys.next();
            if (!other.properties.containsKey(key)) {
                return false;
            }
            Object o1 = properties.get(key);
            Object o2 = other.properties.get(key);

            if (JSONNull.getInstance().equals(o1)) {
                if (JSONNull.getInstance().equals(o2)) {
                    continue;
                } else {
                    return false;
                }
            } else {
                if (JSONNull.getInstance().equals(o2)) {
                    return false;
                }
            }

            if (o1 instanceof String && o2 instanceof JSONFunction) {
                if (!o1.equals(String.valueOf(o2))) {
                    return false;
                }
            } else if (o1 instanceof JSONFunction && o2 instanceof String) {
                if (!o2.equals(String.valueOf(o1))) {
                    return false;
                }
            } else if (o1 instanceof JSONObject && o2 instanceof JSONObject) {
                if (!o1.equals(o2)) {
                    return false;
                }
            } else if (o1 instanceof JSONArray && o2 instanceof JSONArray) {
                if (!o1.equals(o2)) {
                    return false;
                }
            } else if (o1 instanceof JSONFunction && o2 instanceof JSONFunction) {
                if (!o1.equals(o2)) {
                    return false;
                }
            } else {
                if (o1 instanceof String) {
                    if (!o1.equals(String.valueOf(o2))) {
                        return false;
                    }
                } else if (o2 instanceof String) {
                    if (!o2.equals(String.valueOf(o1))) {
                        return false;
                    }
                } else {
                    Morpher m1 = JSONUtils.getMorpherRegistry().getMorpherFor(o1.getClass());
                    Morpher m2 = JSONUtils.getMorpherRegistry().getMorpherFor(o2.getClass());
                    if (m1 != null && m1 != IdentityObjectMorpher.getInstance()) {
                        if (!o1.equals(JSONUtils.getMorpherRegistry().morph(o1.getClass(), o2))) {
                            return false;
                        }
                    } else if (m2 != null && m2 != IdentityObjectMorpher.getInstance()) {
                        if (!JSONUtils.getMorpherRegistry().morph(o1.getClass(), o1).equals(o2)) {
                            return false;
                        }
                    } else {
                        if (!o1.equals(o2)) {
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }

    public Object get(Object key) {
        if (key instanceof String) {
            return get((String) key);
        }
        return null;
    }

    /**
     * Get the value object associated with a key.
     *
     * @param key A key string.
     * @return The object associated with the key.
     * @throws JSONException if this.isNull() returns true.
     */
    public Object get(String key) {
        verifyIsNull();
        return this.properties.get(key);
    }

    /**
     * Get the boolean value associated with a key.
     *
     * @param key A key string.
     * @return The truth.
     * @throws JSONException if the value is not a Boolean or the String "true"
     *         or "false".
     */
    public boolean getBoolean(String key) {
        verifyIsNull();
        Object o = get(key);
        if (o != null) {
            if (o.equals(Boolean.FALSE) || (o instanceof String && ((String) o).equalsIgnoreCase("false"))) {
                return false;
            } else if (o.equals(Boolean.TRUE) || (o instanceof String && ((String) o).equalsIgnoreCase("true"))) {
                return true;
            }
        }
        throw new JSONException("JSONObject[" + JSONUtils.quote(key) + "] is not a Boolean.");
    }

    /**
     * Get the double value associated with a key.
     *
     * @param key A key string.
     * @return The numeric value.
     * @throws JSONException if the key is not found or if the value is not a
     *         Number object and cannot be converted to a number.
     */
    public double getDouble(String key) {
        verifyIsNull();
        Object o = get(key);
        if (o != null) {
            try {
                return o instanceof Number ? ((Number) o).doubleValue() : Double.parseDouble((String) o);
            } catch (Exception e) {
                throw new JSONException("JSONObject[" + JSONUtils.quote(key) + "] is not a number.");
            }
        }
        throw new JSONException("JSONObject[" + JSONUtils.quote(key) + "] is not a number.");
    }

    /**
     * Get the int value associated with a key. If the number value is too large
     * for an int, it will be clipped.
     *
     * @param key A key string.
     * @return The integer value.
     * @throws JSONException if the key is not found or if the value cannot be
     *         converted to an integer.
     */
    public int getInt(String key) {
        verifyIsNull();
        Object o = get(key);
        if (o != null) {
            return o instanceof Number ? ((Number) o).intValue() : (int) getDouble(key);
        }
        throw new JSONException("JSONObject[" + JSONUtils.quote(key) + "] is not a number.");
    }

    /**
     * Get the JSONArray value associated with a key.
     *
     * @param key A key string.
     * @return A JSONArray which is the value.
     * @throws JSONException if the key is not found or if the value is not a
     *         JSONArray.
     */
    public JSONArray getJSONArray(String key) {
        verifyIsNull();
        Object o = get(key);
        if (o != null && o instanceof JSONArray) {
            return (JSONArray) o;
        }
        throw new JSONException("JSONObject[" + JSONUtils.quote(key) + "] is not a JSONArray.");
    }

    /**
     * Get the JSONObject value associated with a key.
     *
     * @param key A key string.
     * @return A JSONObject which is the value.
     * @throws JSONException if the key is not found or if the value is not a
     *         JSONObject.
     */
    public JSONObject getJSONObject(String key) {
        verifyIsNull();
        Object o = get(key);
        if (JSONNull.getInstance().equals(o)) {
            return new JSONObject(true);
        } else if (o instanceof JSONObject) {
            return (JSONObject) o;
        }
        throw new JSONException("JSONObject[" + JSONUtils.quote(key) + "] is not a JSONObject.");
    }

    /**
     * Get the long value associated with a key. If the number value is too long
     * for a long, it will be clipped.
     *
     * @param key A key string.
     * @return The long value.
     * @throws JSONException if the key is not found or if the value cannot be
     *         converted to a long.
     */
    public long getLong(String key) {
        verifyIsNull();
        Object o = get(key);
        if (o != null) {
            return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(key);
        }
        throw new JSONException("JSONObject[" + JSONUtils.quote(key) + "] is not a number.");
    }

    /**
     * Get the string associated with a key.
     *
     * @param key A key string.
     * @return A string which is the value.
     * @throws JSONException if the key is not found.
     */
    public String getString(String key) {
        verifyIsNull();
        Object o = get(key);
        if (o != null) {
            return o.toString();
        }
        throw new JSONException("JSONObject[" + JSONUtils.quote(key) + "] not found.");
    }

    /**
     * Determine if the JSONObject contains a specific key.
     *
     * @param key A key string.
     * @return true if the key exists in the JSONObject.
     */
    public boolean has(String key) {
        verifyIsNull();
        return this.properties.containsKey(key);
    }

    public int hashCode() {
        int hashcode = 19;
        if (isNullObject()) {
            return hashcode + JSONNull.getInstance().hashCode();
        }
        for (Iterator entries = properties.entrySet().iterator(); entries.hasNext();) {
            Map.Entry entry = (Map.Entry) entries.next();
            Object key = entry.getKey();
            Object value = entry.getValue();
            hashcode += key.hashCode() + JSONUtils.hashCode(value);
        }
        return hashcode;
    }

    public boolean isArray() {
        return false;
    }

    public boolean isEmpty() {
        // verifyIsNull();
        return this.properties.isEmpty();
    }

    /**
     * Returs if this object is a null JSONObject.
     */
    public boolean isNullObject() {
        return nullObject;
    }

    /**
     * Get an enumeration of the keys of the JSONObject.
     *
     * @return An iterator of the keys.
     */
    public Iterator keys() {
        verifyIsNull();
        return keySet().iterator();
    }

    public Set keySet() {
        return Collections.unmodifiableSet(properties.keySet());
    }

    /**
     * Produce a JSONArray containing the names of the elements of this
     * JSONObject.
     *
     * @return A JSONArray containing the key strings, or null if the JSONObject
     *         is empty.
     */
    public JSONArray names() {
        return names(new JsonConfig());
    }

    /**
     * Produce a JSONArray containing the names of the elements of this
     * JSONObject.
     *
     * @return A JSONArray containing the key strings, or null if the JSONObject
     *         is empty.
     */
    public JSONArray names(JsonConfig jsonConfig) {
        verifyIsNull();
        JSONArray ja = new JSONArray();
        Iterator keys = keys();
        while (keys.hasNext()) {
            ja.element(keys.next(), jsonConfig);
        }
        return ja;
    }

    /**
     * Get an optional value associated with a key.
     *
     * @param key A key string.
     * @return An object which is the value, or null if there is no value.
     */
    public Object opt(String key) {
        verifyIsNull();
        return key == null ? null : this.properties.get(key);
    }

    /**
     * Get an optional boolean associated with a key. It returns false if there
     * is no such key, or if the value is not Boolean.TRUE or the String "true".
     *
     * @param key A key string.
     * @return The truth.
     */
    public boolean optBoolean(String key) {
        verifyIsNull();
        return optBoolean(key, false);
    }

    /**
     * Get an optional boolean associated with a key. It returns the defaultValue
     * if there is no such key, or if it is not a Boolean or the String "true" or
     * "false" (case insensitive).
     *
     * @param key A key string.
     * @param defaultValue The default.
     * @return The truth.
     */
    public boolean optBoolean(String key, boolean defaultValue) {
        verifyIsNull();
        try {
            return getBoolean(key);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * Get an optional double associated with a key, or NaN if there is no such
     * key or if its value is not a number. If the value is a string, an attempt
     * will be made to evaluate it as a number.
     *
     * @param key A string which is the key.
     * @return An object which is the value.
     */
    public double optDouble(String key) {
        verifyIsNull();
        return optDouble(key, Double.NaN);
    }

    /**
     * Get an optional double associated with a key, or the defaultValue if there
     * is no such key or if its value is not a number. If the value is a string,
     * an attempt will be made to evaluate it as a number.
     *
     * @param key A key string.
     * @param defaultValue The default.
     * @return An object which is the value.
     */
    public double optDouble(String key, double defaultValue) {
        verifyIsNull();
        try {
            Object o = opt(key);
            return o instanceof Number ? ((Number) o).doubleValue() : new Double((String) o).doubleValue();
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * Get an optional int value associated with a key, or zero if there is no
     * such key or if the value is not a number. If the value is a string, an
     * attempt will be made to evaluate it as a number.
     *
     * @param key A key string.
     * @return An object which is the value.
     */
    public int optInt(String key) {
        verifyIsNull();
        return optInt(key, 0);
    }

    /**
     * Get an optional int value associated with a key, or the default if there
     * is no such key or if the value is not a number. If the value is a string,
     * an attempt will be made to evaluate it as a number.
     *
     * @param key A key string.
     * @param defaultValue The default.
     * @return An object which is the value.
     */
    public int optInt(String key, int defaultValue) {
        verifyIsNull();
        try {
            return getInt(key);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * Get an optional JSONArray associated with a key. It returns null if there
     * is no such key, or if its value is not a JSONArray.
     *
     * @param key A key string.
     * @return A JSONArray which is the value.
     */
    public JSONArray optJSONArray(String key) {
        verifyIsNull();
        Object o = opt(key);
        return o instanceof JSONArray ? (JSONArray) o : null;
    }

    /**
     * Get an optional JSONObject associated with a key. It returns null if there
     * is no such key, or if its value is not a JSONObject.
     *
     * @param key A key string.
     * @return A JSONObject which is the value.
     */
    public JSONObject optJSONObject(String key) {
        verifyIsNull();
        Object o = opt(key);
        return o instanceof JSONObject ? (JSONObject) o : null;
    }

    /**
     * Get an optional long value associated with a key, or zero if there is no
     * such key or if the value is not a number. If the value is a string, an
     * attempt will be made to evaluate it as a number.
     *
     * @param key A key string.
     * @return An object which is the value.
     */
    public long optLong(String key) {
        verifyIsNull();
        return optLong(key, 0);
    }

    /**
     * Get an optional long value associated with a key, or the default if there
     * is no such key or if the value is not a number. If the value is a string,
     * an attempt will be made to evaluate it as a number.
     *
     * @param key A key string.
     * @param defaultValue The default.
     * @return An object which is the value.
     */
    public long optLong(String key, long defaultValue) {
        verifyIsNull();
        try {
            return getLong(key);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * Get an optional string associated with a key. It returns an empty string
     * if there is no such key. If the value is not a string and is not null,
     * then it is coverted to a string.
     *
     * @param key A key string.
     * @return A string which is the value.
     */
    public String optString(String key) {
        verifyIsNull();
        return optString(key, "");
    }

    /**
     * Get an optional string associated with a key. It returns the defaultValue
     * if there is no such key.
     *
     * @param key A key string.
     * @param defaultValue The default.
     * @return A string which is the value.
     */
    public String optString(String key, String defaultValue) {
        verifyIsNull();
        Object o = opt(key);
        return o != null ? o.toString() : defaultValue;
    }

    public Object put(Object key, Object value) {
        if (key == null) {
            throw new IllegalArgumentException("key is null.");
        }
        Object previous = properties.get(key);
        element(String.valueOf(key), value);
        return previous;
    }

    public void putAll(Map map) {
        putAll(map, new JsonConfig());
    }

    public void putAll(Map map, JsonConfig jsonConfig) {
        if (map instanceof JSONObject) {
            for (Iterator entries = map.entrySet().iterator(); entries.hasNext();) {
                Map.Entry entry = (Map.Entry) entries.next();
                String key = (String) entry.getKey();
                Object value = entry.getValue();
                this.properties.put(key, value);
            }
        } else {
            for (Iterator entries = map.entrySet().iterator(); entries.hasNext();) {
                Map.Entry entry = (Map.Entry) entries.next();
                String key = String.valueOf(entry.getKey());
                Object value = entry.getValue();
                element(key, value, jsonConfig);
            }
        }
    }

    public Object remove(Object key) {
        return properties.remove(key);
    }

    /**
     * Remove a name and its value, if present.
     *
     * @param key The name to be removed.
     * @return The value that was associated with the name, or null if there was
     *         no value.
     */
    public Object remove(String key) {
        verifyIsNull();
        return this.properties.remove(key);
    }

    /**
     * Get the number of keys stored in the JSONObject.
     *
     * @return The number of keys in the JSONObject.
     */
    public int size() {
        // verifyIsNull();
        return this.properties.size();
    }

    /**
     * Produce a JSONArray containing the values of the members of this
     * JSONObject.
     *
     * @param names A JSONArray containing a list of key strings. This determines
     *        the sequence of the values in the result.
     * @return A JSONArray of values.
     * @throws JSONException If any of the values are non-finite numbers.
     */
    public JSONArray toJSONArray(JSONArray names) {
        verifyIsNull();
        if (names == null || names.size() == 0) {
            return null;
        }
        JSONArray ja = new JSONArray();
        for (int i = 0; i < names.size(); i += 1) {
            ja.element(this.opt(names.getString(i)));
        }
        return ja;
    }

    /**
     * Make a JSON text of this JSONObject. For compactness, no whitespace is
     * added. If this would not result in a syntactically correct JSON text, then
     * null will be returned instead.
     * <p>
     * Warning: This method assumes that the data structure is acyclical.
     *
     * @return a printable, displayable, portable, transmittable representation
     *         of the object, beginning with <code>{</code>&nbsp;<small>(left
     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
     *         brace)</small>.
     */
    public String toString() {
        if (isNullObject()) {
            return JSONNull.getInstance().toString();
        }
        try {
            Iterator keys = keys();
            StringBuffer sb = new StringBuffer("{");

            while (keys.hasNext()) {
                if (sb.length() > 1) {
                    sb.append(',');
                }
                Object o = keys.next();
                sb.append(JSONUtils.quote(o.toString()));
                sb.append(':');
                sb.append(JSONUtils.valueToString(this.properties.get(o)));
            }
            sb.append('}');
            return sb.toString();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Make a prettyprinted JSON text of this JSONObject.
     * <p>
     * Warning: This method assumes that the data structure is acyclical.
     *
     * @param indentFactor The number of spaces to add to each level of
     *        indentation.
     * @return a printable, displayable, portable, transmittable representation
     *         of the object, beginning with <code>{</code>&nbsp;<small>(left
     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
     *         brace)</small>.
     * @throws JSONException If the object contains an invalid number.
     */
    public String toString(int indentFactor) {
        if (isNullObject()) {
            return JSONNull.getInstance().toString();
        }
        if (indentFactor == 0) {
            return this.toString();
        }
        return toString(indentFactor, 0);
    }

    /**
     * Make a prettyprinted JSON text of this JSONObject.
     * <p>
     * Warning: This method assumes that the data structure is acyclical.
     *
     * @param indentFactor The number of spaces to add to each level of
     *        indentation.
     * @param indent The indentation of the top level.
     * @return a printable, displayable, transmittable representation of the
     *         object, beginning with <code>{</code>&nbsp;<small>(left brace)</small>
     *         and ending with <code>}</code>&nbsp;<small>(right brace)</small>.
     * @throws JSONException If the object contains an invalid number.
     */
    public String toString(int indentFactor, int indent) {
        if (isNullObject()) {
            return JSONNull.getInstance().toString();
        }
        int i;
        int n = size();
        if (n == 0) {
            return "{}";
        }
        if (indentFactor == 0) {
            return this.toString();
        }
        Iterator keys = keys();
        StringBuffer sb = new StringBuffer("{");
        int newindent = indent + indentFactor;
        Object o;
        if (n == 1) {
            o = keys.next();
            sb.append(JSONUtils.quote(o.toString()));
            sb.append(": ");
            sb.append(JSONUtils.valueToString(this.properties.get(o), indentFactor, indent));
        } else {
            while (keys.hasNext()) {
                o = keys.next();
                if (sb.length() > 1) {
                    sb.append(",\n");
                } else {
                    sb.append('\n');
                }
                for (i = 0; i < newindent; i += 1) {
                    sb.append(' ');
                }
                sb.append(JSONUtils.quote(o.toString()));
                sb.append(": ");
                sb.append(JSONUtils.valueToString(this.properties.get(o), indentFactor, newindent));
            }
            if (sb.length() > 1) {
                sb.append('\n');
                for (i = 0; i < indent; i += 1) {
                    sb.append(' ');
                }
            }
            for (i = 0; i < indent; i += 1) {
                sb.insert(0, ' ');
            }
        }
        sb.append('}');
        return sb.toString();
    }

    public Collection values() {
        return Collections.unmodifiableCollection(properties.values());
    }

    /**
     * Write the contents of the JSONObject as JSON text to a writer. For
     * compactness, no whitespace is added.
     * <p>
     * Warning: This method assumes that the data structure is acyclical.
     *
     * @return The writer.
     * @throws JSONException
     */
    public Writer write(Writer writer) {
        try {
            if (isNullObject()) {
                writer.write(JSONNull.getInstance().toString());
                return writer;
            }

            boolean b = false;
            Iterator keys = keys();
            writer.write('{');

            while (keys.hasNext()) {
                if (b) {
                    writer.write(',');
                }
                Object k = keys.next();
                writer.write(JSONUtils.quote(k.toString()));
                writer.write(':');
                Object v = this.properties.get(k);
                if (v instanceof JSONObject) {
                    ((JSONObject) v).write(writer);
                } else if (v instanceof JSONArray) {
                    ((JSONArray) v).write(writer);
                } else {
                    writer.write(JSONUtils.valueToString(v));
                }
                b = true;
            }
            writer.write('}');
            return writer;
        } catch (IOException e) {
            throw new JSONException(e);
        }
    }

    private JSONObject _accumulate(String key, Object value, JsonConfig jsonConfig) {
        if (isNullObject()) {
            throw new JSONException("Can't accumulate on null object");
        }

        if (!has(key)) {
            setInternal(key, value, jsonConfig);
        } else {
            Object o = opt(key);
            if (o instanceof JSONArray) {
                ((JSONArray) o).element(value, jsonConfig);
            } else {
                setInternal(key, new JSONArray().element(o).element(value, jsonConfig), jsonConfig);
            }
        }

        return this;
    }

    protected Object _processValue(Object value, JsonConfig jsonConfig) {
        if (value instanceof JSONTokener) {
            return _fromJSONTokener((JSONTokener) value, jsonConfig);
        }
        return super._processValue(value, jsonConfig);
    }

    /**
     * Put a key/value pair in the JSONObject.
     *
     * @param key A key string.
     * @param value An object which is the value. It should be of one of these
     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
     *        String, or the JSONNull object.
     * @return this.
     * @throws JSONException If the value is non-finite number or if the key is
     *         null.
     */
    private JSONObject _setInternal(String key, Object value, JsonConfig jsonConfig) {
        verifyIsNull();
        if (key == null) {
            throw new JSONException("Null key.");
        }

        if (JSONUtils.isString(value) && JSONUtils.mayBeJSON(String.valueOf(value))) {
            this.properties.put(key, value);
        } else {
            /*
            Object jo = _processValue( value, jsonConfig );
            if( CycleDetectionStrategy.IGNORE_PROPERTY_OBJ == jo
                  || CycleDetectionStrategy.IGNORE_PROPERTY_ARR == jo ){
               // do nothing
            }else{
               this.properties.put( key, jo );
            }
            */
            if (CycleDetectionStrategy.IGNORE_PROPERTY_OBJ == value
                    || CycleDetectionStrategy.IGNORE_PROPERTY_ARR == value) {
                // do nothing
            } else {
                this.properties.put(key, value);
            }
        }

        return this;
    }

    private Object processValue(Object value, JsonConfig jsonConfig) {
        if (value != null) {
            JsonValueProcessor processor = jsonConfig.findJsonValueProcessor(value.getClass());
            if (processor != null) {
                value = processor.processObjectValue(null, value, jsonConfig);
                if (!JsonVerifier.isValidJsonValue(value)) {
                    throw new JSONException("Value is not a valid JSON value. " + value);
                }
            }
        }
        return _processValue(value, jsonConfig);
    }

    private Object processValue(String key, Object value, JsonConfig jsonConfig) {
        if (value != null) {
            JsonValueProcessor processor = jsonConfig.findJsonValueProcessor(value.getClass(), key);
            if (processor != null) {
                value = processor.processObjectValue(null, value, jsonConfig);
                if (!JsonVerifier.isValidJsonValue(value)) {
                    throw new JSONException("Value is not a valid JSON value. " + value);
                }
            }
        }
        return _processValue(value, jsonConfig);
    }

    /**
     * Put a key/value pair in the JSONObject.
     *
     * @param key A key string.
     * @param value An object which is the value. It should be of one of these
     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
     *        String, or the JSONNull object.
     * @return this.
     * @throws JSONException If the value is non-finite number or if the key is
     *         null.
     */
    private JSONObject setInternal(String key, Object value, JsonConfig jsonConfig) {
        return _setInternal(key, processValue(key, value, jsonConfig), jsonConfig);
    }

    /**
     * Checks if this object is a "null" object.
     */
    private void verifyIsNull() {
        if (isNullObject()) {
            throw new JSONException("null object");
        }
    }
}