View Javadoc

1   /*
2    * Copyright 2002-2009 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.json;
17  
18  import java.beans.PropertyDescriptor;
19  import java.io.IOException;
20  import java.io.Writer;
21  import java.lang.reflect.Array;
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Modifier;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import net.sf.ezmorph.Morpher;
33  import net.sf.ezmorph.array.ObjectArrayMorpher;
34  import net.sf.ezmorph.bean.BeanMorpher;
35  import net.sf.ezmorph.object.IdentityObjectMorpher;
36  import net.sf.json.processors.JsonBeanProcessor;
37  import net.sf.json.processors.JsonValueProcessor;
38  import net.sf.json.processors.JsonVerifier;
39  import net.sf.json.processors.PropertyNameProcessor;
40  import net.sf.json.regexp.RegexpUtils;
41  import net.sf.json.util.CycleDetectionStrategy;
42  import net.sf.json.util.JSONTokener;
43  import net.sf.json.util.JSONUtils;
44  import net.sf.json.util.PropertyFilter;
45  import net.sf.json.util.PropertySetStrategy;
46  
47  import org.apache.commons.beanutils.DynaBean;
48  import org.apache.commons.beanutils.DynaProperty;
49  import org.apache.commons.beanutils.PropertyUtils;
50  import org.apache.commons.collections.map.ListOrderedMap;
51  import org.apache.commons.lang.StringUtils;
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  
55  /**
56   * A JSONObject is an unordered collection of name/value pairs. Its external
57   * form is a string wrapped in curly braces with colons between the names and
58   * values, and commas between the values and names. The internal form is an
59   * object having <code>get</code> and <code>opt</code> methods for accessing
60   * the values by name, and <code>put</code> methods for adding or replacing
61   * values by name. The values can be any of these types: <code>Boolean</code>,
62   * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>,
63   * <code>String</code>, or the <code>JSONNull</code> object. A JSONObject
64   * constructor can be used to convert an external form JSON text into an
65   * internal form whose values can be retrieved with the <code>get</code> and
66   * <code>opt</code> methods, or to convert values into a JSON text using the
67   * <code>element</code> and <code>toString</code> methods. A
68   * <code>get</code> method returns a value if one can be found, and throws an
69   * exception if one cannot be found. An <code>opt</code> method returns a
70   * default value instead of throwing an exception, and so is useful for
71   * obtaining optional values.
72   * <p>
73   * The generic <code>get()</code> and <code>opt()</code> methods return an
74   * object, which you can cast or query for type. There are also typed
75   * <code>get</code> and <code>opt</code> methods that do type checking and
76   * type coercion for you.
77   * <p>
78   * The <code>put</code> methods adds values to an object. For example,
79   *
80   * <pre>
81   *     myString = new JSONObject().put("JSON", "Hello, World!").toString();</pre>
82   *
83   * produces the string <code>{"JSON": "Hello, World"}</code>.
84   * <p>
85   * The texts produced by the <code>toString</code> methods strictly conform to
86   * the JSON syntax rules. The constructors are more forgiving in the texts they
87   * will accept:
88   * <ul>
89   * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
90   * before the closing brace.</li>
91   * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single quote)</small>.</li>
92   * <li>Strings do not need to be quoted at all if they do not begin with a
93   * quote or single quote, and if they do not contain leading or trailing spaces,
94   * and if they do not contain any of these characters:
95   * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and
96   * if they are not the reserved words <code>true</code>, <code>false</code>,
97   * or <code>null</code>.</li>
98   * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as
99   * by <code>:</code>.</li>
100  * <li>Values can be followed by <code>;</code> <small>(semicolon)</small>
101  * as well as by <code>,</code> <small>(comma)</small>.</li>
102  * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or
103  * <code>0x-</code> <small>(hex)</small> prefix.</li>
104  * <li>Comments written in the slashshlash, slashstar, and hash conventions
105  * will be ignored.</li>
106  * </ul>
107  *
108  * @author JSON.org
109  */
110 public final class JSONObject extends AbstractJSON implements JSON, Map, Comparable {
111 
112    private static final Log log = LogFactory.getLog( JSONObject.class );
113 
114    /**
115     * Creates a JSONObject.<br>
116     * Inspects the object type to call the correct JSONObject factory method.
117     * Accepts JSON formatted strings, Maps, DynaBeans and JavaBeans.
118     *
119     * @param object
120     * @throws JSONException if the object can not be converted to a proper
121     *         JSONObject.
122     */
123 
124    public static JSONObject fromObject( Object object ) {
125       return fromObject( object, new JsonConfig() );
126    }
127 
128    /**
129     * Creates a JSONObject.<br>
130     * Inspects the object type to call the correct JSONObject factory method.
131     * Accepts JSON formatted strings, Maps, DynaBeans and JavaBeans.
132     *
133     * @param object
134     * @throws JSONException if the object can not be converted to a proper
135     *         JSONObject.
136     */
137    public static JSONObject fromObject( Object object, JsonConfig jsonConfig ) {
138       if( object == null || JSONUtils.isNull( object ) ){
139          return new JSONObject( true );
140       }else if( object instanceof JSONObject ){
141          return _fromJSONObject( (JSONObject) object, jsonConfig );
142       }else if( object instanceof DynaBean ){
143          return _fromDynaBean( (DynaBean) object, jsonConfig );
144       }else if( object instanceof JSONTokener ){
145          return _fromJSONTokener( (JSONTokener) object, jsonConfig );
146       }else if( object instanceof JSONString ){
147          return _fromJSONString( (JSONString) object, jsonConfig );
148       }else if( object instanceof Map ){
149          return _fromMap( (Map) object, jsonConfig );
150       }else if( object instanceof String ){
151          return _fromString( (String) object, jsonConfig );
152       }else if( JSONUtils.isNumber( object ) || JSONUtils.isBoolean( object )
153             || JSONUtils.isString( object ) ){
154          return new JSONObject();
155       }else if( JSONUtils.isArray( object ) ){
156          throw new JSONException( "'object' is an array. Use JSONArray instead" );
157       }else{
158          return _fromBean( object, jsonConfig );
159       }
160    }
161 
162    /**
163     * Creates a JSONDynaBean from a JSONObject.
164     */
165    public static Object toBean( JSONObject jsonObject ) {
166       if( jsonObject == null || jsonObject.isNullObject() ){
167          return null;
168       }
169 
170       DynaBean dynaBean = null;
171 
172       JsonConfig jsonConfig = new JsonConfig();
173       Map props = JSONUtils.getProperties( jsonObject );
174       dynaBean = JSONUtils.newDynaBean( jsonObject, jsonConfig );
175       for( Iterator entries = jsonObject.names( jsonConfig )
176             .iterator(); entries.hasNext(); ){
177          String name = (String) entries.next();
178          String key = JSONUtils.convertToJavaIdentifier( name, jsonConfig );
179          Class type = (Class) props.get( name );
180          Object value = jsonObject.get( name );
181          try{
182             if( !JSONUtils.isNull( value ) ){
183                if( value instanceof JSONArray ){
184                   dynaBean.set( key, JSONArray.toCollection( (JSONArray) value ) );
185                }else if( String.class.isAssignableFrom( type )
186                      || Boolean.class.isAssignableFrom( type ) || JSONUtils.isNumber( type )
187                      || Character.class.isAssignableFrom( type )
188                      || JSONFunction.class.isAssignableFrom( type ) ){
189                   dynaBean.set( key, value );
190                }else{
191                   dynaBean.set( key, toBean( (JSONObject) value ) );
192                }
193             }else{
194                if( type.isPrimitive() ){
195                   // assume assigned default value
196                   log.warn( "Tried to assign null value to " + key + ":" + type.getName() );
197                   dynaBean.set( key, JSONUtils.getMorpherRegistry()
198                         .morph( type, null ) );
199                }else{
200                   dynaBean.set( key, null );
201                }
202             }
203          }catch( JSONException jsone ){
204             throw jsone;
205          }catch( Exception e ){
206             throw new JSONException( "Error while setting property=" + name + " type" + type, e );
207          }
208       }
209 
210       return dynaBean;
211    }
212 
213    /**
214     * Creates a bean from a JSONObject, with a specific target class.<br>
215     */
216    public static Object toBean( JSONObject jsonObject, Class beanClass ) {
217       JsonConfig jsonConfig = new JsonConfig();
218       jsonConfig.setRootClass( beanClass );
219       return toBean( jsonObject, jsonConfig );
220    }
221 
222    /**
223     * Creates a bean from a JSONObject, with a specific target class.<br>
224     * If beanClass is null, this method will return a graph of DynaBeans. Any
225     * attribute that is a JSONObject and matches a key in the classMap will be
226     * converted to that target class.<br>
227     * The classMap has the following conventions:
228     * <ul>
229     * <li>Every key must be an String.</li>
230     * <li>Every value must be a Class.</li>
231     * <li>A key may be a regular expression.</li>
232     * </ul>
233     */
234    public static Object toBean( JSONObject jsonObject, Class beanClass, Map classMap ) {
235       JsonConfig jsonConfig = new JsonConfig();
236       jsonConfig.setRootClass( beanClass );
237       jsonConfig.setClassMap( classMap );
238       return toBean( jsonObject, jsonConfig );
239    }
240 
241    /**
242     * Creates a bean from a JSONObject, with the specific configuration.
243     */
244    public static Object toBean( JSONObject jsonObject, JsonConfig jsonConfig ) {
245       if( jsonObject == null || jsonObject.isNullObject() ){
246          return null;
247       }
248 
249       Class beanClass = jsonConfig.getRootClass();
250       Map classMap = jsonConfig.getClassMap();
251 
252       if( beanClass == null ){
253          return toBean( jsonObject );
254       }
255       if( classMap == null ){
256          classMap = Collections.EMPTY_MAP;
257       }
258 
259       Object bean = null;
260       try{
261          if( beanClass.isInterface() ){
262             if( !Map.class.isAssignableFrom( beanClass ) ){
263                throw new JSONException( "beanClass is an interface. " + beanClass );
264             }else{
265                bean = new HashMap();
266             }
267          }else{
268             bean = jsonConfig.getNewBeanInstanceStrategy()
269                   .newInstance( beanClass, jsonObject );
270          }
271       }catch( JSONException jsone ){
272          throw jsone;
273       }catch( Exception e ){
274          throw new JSONException( e );
275       }
276 
277       Map props = JSONUtils.getProperties( jsonObject );
278       PropertyFilter javaPropertyFilter = jsonConfig.getJavaPropertyFilter();
279       for( Iterator entries = jsonObject.names( jsonConfig )
280             .iterator(); entries.hasNext(); ){
281          String name = (String) entries.next();
282          Class type = (Class) props.get( name );
283          Object value = jsonObject.get( name );
284          if( javaPropertyFilter != null && javaPropertyFilter.apply( bean, name, value ) ){
285             continue;
286          }
287          String key = Map.class.isAssignableFrom( beanClass )
288                && jsonConfig.isSkipJavaIdentifierTransformationInMapKeys() ? name
289                : JSONUtils.convertToJavaIdentifier( name, jsonConfig );
290          PropertyNameProcessor propertyNameProcessor = jsonConfig.findJavaPropertyNameProcessor( beanClass );
291          if( propertyNameProcessor != null ){
292             key = propertyNameProcessor.processPropertyName( beanClass, key );
293          }
294          try{
295             if( Map.class.isAssignableFrom( beanClass ) ){
296                // no type info available for conversion
297                if( JSONUtils.isNull( value ) ){
298                   setProperty( bean, key, value, jsonConfig );
299                }else if( value instanceof JSONArray ){
300                   setProperty( bean, key, convertPropertyValueToCollection( key, value, jsonConfig, name,
301                         classMap, List.class ), jsonConfig );
302                }else if( String.class.isAssignableFrom( type ) || JSONUtils.isBoolean( type )
303                      || JSONUtils.isNumber( type ) || JSONUtils.isString( type )
304                      || JSONFunction.class.isAssignableFrom( type ) ){
305                   if( jsonConfig.isHandleJettisonEmptyElement() && "".equals( value ) ){
306                      setProperty( bean, key, null, jsonConfig );
307                   }else{
308                      setProperty( bean, key, value, jsonConfig );
309                   }
310                }else{
311                   Class targetClass = findTargetClass( key, classMap );
312                   targetClass = targetClass == null ? findTargetClass( name, classMap )
313                         : targetClass;
314                   JsonConfig jsc = jsonConfig.copy();
315                   jsc.setRootClass( targetClass );
316                   jsc.setClassMap( classMap );
317                   if( targetClass != null ){
318                      setProperty( bean, key, toBean( (JSONObject) value, jsc ), jsonConfig );
319                   }else{
320                      setProperty( bean, key, toBean( (JSONObject) value ), jsonConfig );
321                   }
322                }
323             }else{
324                PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor( bean, key );
325                if( pd != null && pd.getWriteMethod() == null ){
326                   log.info( "Property '" + key + "' of "+ bean.getClass()+" has no write method. SKIPPED." );
327                   continue;
328                }
329 
330                if( pd != null ){
331                   Class targetType = pd.getPropertyType();
332                   if( !JSONUtils.isNull( value ) ){
333                      if( value instanceof JSONArray ){
334                         if( List.class.isAssignableFrom( pd.getPropertyType() ) ){
335                            setProperty( bean, key, convertPropertyValueToCollection( key, value,
336                                  jsonConfig, name, classMap, pd.getPropertyType() ), jsonConfig );
337                         }else if( Set.class.isAssignableFrom( pd.getPropertyType() ) ){
338                            setProperty( bean, key, convertPropertyValueToCollection( key, value,
339                                  jsonConfig, name, classMap, pd.getPropertyType() ), jsonConfig );
340                         }else{
341                            setProperty( bean, key, convertPropertyValueToArray( key, value,
342                                  targetType, jsonConfig, classMap ), jsonConfig );
343                         }
344                      }else if( String.class.isAssignableFrom( type ) || JSONUtils.isBoolean( type )
345                            || JSONUtils.isNumber( type ) || JSONUtils.isString( type )
346                            || JSONFunction.class.isAssignableFrom( type ) ){
347                         if( pd != null ){
348                            if( jsonConfig.isHandleJettisonEmptyElement() && "".equals( value ) ){
349                               setProperty( bean, key, null, jsonConfig );
350                            }else if( !targetType.isInstance( value ) ){
351                               setProperty( bean, key, morphPropertyValue( key, value, type,
352                                     targetType ), jsonConfig );
353                            }else{
354                               setProperty( bean, key, value, jsonConfig );
355                            }
356                         }else if( beanClass == null || bean instanceof Map ){
357                            setProperty( bean, key, value, jsonConfig );
358                         }else{
359                            log.warn( "Tried to assign property " + key + ":" + type.getName()
360                                  + " to bean of class " + bean.getClass()
361                                        .getName() );
362                         }
363                      }else{
364                         if( jsonConfig.isHandleJettisonSingleElementArray() ){
365                            JSONArray array = new JSONArray().element( value, jsonConfig );
366                            Class newTargetClass = findTargetClass( key, classMap );
367                            newTargetClass = newTargetClass == null ? findTargetClass( name,
368                                  classMap ) : newTargetClass;
369                            JsonConfig jsc = jsonConfig.copy();
370                            jsc.setRootClass( newTargetClass );
371                            jsc.setClassMap( classMap );
372                            if( targetType.isArray() ){
373                               setProperty( bean, key, JSONArray.toArray( array, jsc ), jsonConfig );
374                            }else if( JSONArray.class.isAssignableFrom( targetType ) ){
375                               setProperty( bean, key, array, jsonConfig );
376                            }else if( List.class.isAssignableFrom( targetType )
377                                  || Set.class.isAssignableFrom( targetType ) ){
378                               jsc.setCollectionType( targetType );
379                               setProperty( bean, key, JSONArray.toCollection( array, jsc ),
380                                     jsonConfig );
381                            }else{
382                               setProperty( bean, key, toBean( (JSONObject) value, jsc ), jsonConfig );
383                            }
384                         }else{
385                            if( targetType == Object.class || targetType.isInterface() ) {
386                               Class targetTypeCopy = targetType;
387                               targetType = findTargetClass( key, classMap );
388                               targetType = targetType == null ? findTargetClass( name, classMap )
389                                     : targetType;
390                               targetType = targetType == null && targetTypeCopy.isInterface() ? targetTypeCopy
391                                     : targetType;
392                            }
393                            JsonConfig jsc = jsonConfig.copy();
394                            jsc.setRootClass( targetType );
395                            jsc.setClassMap( classMap );
396                            setProperty( bean, key, toBean( (JSONObject) value, jsc ), jsonConfig );
397                         }
398                      }
399                   }else{
400                      if( type.isPrimitive() ){
401                         // assume assigned default value
402                         log.warn( "Tried to assign null value to " + key + ":" + type.getName() );
403                         setProperty( bean, key, JSONUtils.getMorpherRegistry()
404                               .morph( type, null ), jsonConfig );
405                      }else{
406                         setProperty( bean, key, null, jsonConfig );
407                      }
408                   }
409                }else{
410                   // pd is null
411                   if( !JSONUtils.isNull( value ) ){
412                      if( value instanceof JSONArray ){
413                         setProperty( bean, key, convertPropertyValueToCollection( key, value,
414                               jsonConfig, name, classMap, List.class ), jsonConfig );
415                      }else if( String.class.isAssignableFrom( type ) || JSONUtils.isBoolean( type )
416                            || JSONUtils.isNumber( type ) || JSONUtils.isString( type )
417                            || JSONFunction.class.isAssignableFrom( type ) ){
418                         if( beanClass == null || bean instanceof Map || jsonConfig.getPropertySetStrategy() != null || 
419                             !jsonConfig.isIgnorePublicFields() ){
420                            setProperty( bean, key, value, jsonConfig );
421                         }else{
422                            log.warn( "Tried to assign property " + key + ":" + type.getName()
423                                  + " to bean of class " + bean.getClass()
424                                        .getName() );
425                         }
426                      }else{
427                         if( jsonConfig.isHandleJettisonSingleElementArray() ){
428                            Class newTargetClass = findTargetClass( key, classMap );
429                            newTargetClass = newTargetClass == null ? findTargetClass( name,
430                                  classMap ) : newTargetClass;
431                            JsonConfig jsc = jsonConfig.copy();
432                            jsc.setRootClass( newTargetClass );
433                            jsc.setClassMap( classMap );
434                            setProperty( bean, key, toBean( (JSONObject) value, jsc ), jsonConfig );
435                         }else{
436                            setProperty( bean, key, value, jsonConfig );
437                         }
438                      }
439                   }else{
440                      if( type.isPrimitive() ){
441                         // assume assigned default value
442                         log.warn( "Tried to assign null value to " + key + ":" + type.getName() );
443                         setProperty( bean, key, JSONUtils.getMorpherRegistry()
444                               .morph( type, null ), jsonConfig );
445                      }else{
446                         setProperty( bean, key, null, jsonConfig );
447                      }
448                   }
449                }
450             }
451          }catch( JSONException jsone ){
452             throw jsone;
453          }catch( Exception e ){
454             throw new JSONException( "Error while setting property=" + name + " type " + type, e );
455          }
456       }
457 
458       return bean;
459    }
460 
461    /**
462     * Creates a bean from a JSONObject, with the specific configuration.
463     */
464    public static Object toBean( JSONObject jsonObject, Object root, JsonConfig jsonConfig ) {
465       if( jsonObject == null || jsonObject.isNullObject() || root == null ){
466          return root;
467       }
468 
469       Class rootClass = root.getClass();
470       if( rootClass.isInterface() ){
471          throw new JSONException( "Root bean is an interface. " + rootClass );
472       }
473 
474       Map classMap = jsonConfig.getClassMap();
475       if( classMap == null ){
476          classMap = Collections.EMPTY_MAP;
477       }
478 
479       Map props = JSONUtils.getProperties( jsonObject );
480       PropertyFilter javaPropertyFilter = jsonConfig.getJavaPropertyFilter();
481       for( Iterator entries = jsonObject.names( jsonConfig )
482             .iterator(); entries.hasNext(); ){
483          String name = (String) entries.next();
484          Class type = (Class) props.get( name );
485          Object value = jsonObject.get( name );
486          if( javaPropertyFilter != null && javaPropertyFilter.apply( root, name, value ) ){
487             continue;
488          }
489          String key = JSONUtils.convertToJavaIdentifier( name, jsonConfig );
490          try{
491             PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor( root, key );
492             if( pd != null && pd.getWriteMethod() == null ){
493                log.info( "Property '" + key + "' of "+ root.getClass()+" has no write method. SKIPPED." );
494                continue;
495             }
496 
497             if( !JSONUtils.isNull( value ) ){
498                if( value instanceof JSONArray ){
499                   if( pd == null || List.class.isAssignableFrom( pd.getPropertyType() ) ){
500                      Class targetClass = findTargetClass( key, classMap );
501                      targetClass = targetClass == null ? findTargetClass( name, classMap )
502                            : targetClass;
503                      Object newRoot = jsonConfig.getNewBeanInstanceStrategy()
504                            .newInstance( targetClass, null );
505                      List list = JSONArray.toList( (JSONArray) value, newRoot, jsonConfig );
506                      setProperty( root, key, list, jsonConfig );
507                   }else{
508                      Class innerType = JSONUtils.getInnerComponentType( pd.getPropertyType() );
509                      Class targetInnerType = findTargetClass( key, classMap );
510                      if( innerType.equals( Object.class ) && targetInnerType != null
511                            && !targetInnerType.equals( Object.class ) ){
512                         innerType = targetInnerType;
513                      }
514                      Object newRoot = jsonConfig.getNewBeanInstanceStrategy()
515                            .newInstance( innerType, null );
516                      Object array = JSONArray.toArray( (JSONArray) value, newRoot, jsonConfig );
517                      if( innerType.isPrimitive() || JSONUtils.isNumber( innerType )
518                            || Boolean.class.isAssignableFrom( innerType )
519                            || JSONUtils.isString( innerType ) ){
520                         array = JSONUtils.getMorpherRegistry()
521                               .morph( Array.newInstance( innerType, 0 )
522                                     .getClass(), array );
523                      }else if( !array.getClass()
524                            .equals( pd.getPropertyType() ) ){
525                         if( !pd.getPropertyType()
526                               .equals( Object.class ) ){
527                            Morpher morpher = JSONUtils.getMorpherRegistry()
528                                  .getMorpherFor( Array.newInstance( innerType, 0 )
529                                        .getClass() );
530                            if( IdentityObjectMorpher.getInstance()
531                                  .equals( morpher ) ){
532                               ObjectArrayMorpher beanMorpher = new ObjectArrayMorpher(
533                                     new BeanMorpher( innerType, JSONUtils.getMorpherRegistry() ) );
534                               JSONUtils.getMorpherRegistry()
535                                     .registerMorpher( beanMorpher );
536                            }
537                            array = JSONUtils.getMorpherRegistry()
538                                  .morph( Array.newInstance( innerType, 0 )
539                                        .getClass(), array );
540                         }
541                      }
542                      setProperty( root, key, array, jsonConfig );
543                   }
544                }else if( String.class.isAssignableFrom( type ) || JSONUtils.isBoolean( type )
545                      || JSONUtils.isNumber( type ) || JSONUtils.isString( type )
546                      || JSONFunction.class.isAssignableFrom( type ) ){
547                   if( pd != null ){
548                      if( jsonConfig.isHandleJettisonEmptyElement() && "".equals( value ) ){
549                         setProperty( root, key, null, jsonConfig );
550                      }else if( !pd.getPropertyType()
551                            .isInstance( value ) ){
552                         Morpher morpher = JSONUtils.getMorpherRegistry()
553                               .getMorpherFor( pd.getPropertyType() );
554                         if( IdentityObjectMorpher.getInstance()
555                               .equals( morpher ) ){
556                            log.warn( "Can't transform property '" + key + "' from "
557                                  + type.getName() + " into " + pd.getPropertyType()
558                                        .getName() + ". Will register a default BeanMorpher" );
559                            JSONUtils.getMorpherRegistry()
560                                  .registerMorpher(
561                                        new BeanMorpher( pd.getPropertyType(),
562                                              JSONUtils.getMorpherRegistry() ) );
563                         }
564                         setProperty( root, key, JSONUtils.getMorpherRegistry()
565                               .morph( pd.getPropertyType(), value ), jsonConfig );
566                      }else{
567                         setProperty( root, key, value, jsonConfig );
568                      }
569                   }else if( root instanceof Map ){
570                      setProperty( root, key, value, jsonConfig );
571                   }else{
572                      log.warn( "Tried to assign property " + key + ":" + type.getName()
573                            + " to bean of class " + root.getClass()
574                                  .getName() );
575                   }
576                }else{
577                   if( pd != null ){
578                      Class targetClass = pd.getPropertyType();
579                      if( jsonConfig.isHandleJettisonSingleElementArray() ){
580                         JSONArray array = new JSONArray().element( value, jsonConfig );
581                         Class newTargetClass = findTargetClass( key, classMap );
582                         newTargetClass = newTargetClass == null ? findTargetClass( name, classMap )
583                               : newTargetClass;
584                         Object newRoot = jsonConfig.getNewBeanInstanceStrategy()
585                               .newInstance( newTargetClass, null );
586                         if( targetClass.isArray() ){
587                            setProperty( root, key, JSONArray.toArray( array, newRoot, jsonConfig ),
588                                  jsonConfig );
589                         }else if( Collection.class.isAssignableFrom( targetClass ) ){
590                            setProperty( root, key, JSONArray.toList( array, newRoot, jsonConfig ),
591                                  jsonConfig );
592                         }else if( JSONArray.class.isAssignableFrom( targetClass ) ){
593                            setProperty( root, key, array, jsonConfig );
594                         }else{
595                            setProperty( root, key,
596                                  toBean( (JSONObject) value, newRoot, jsonConfig ), jsonConfig );
597                         }
598                      }else{
599                         if( targetClass == Object.class ){
600                            targetClass = findTargetClass( key, classMap );
601                            targetClass = targetClass == null ? findTargetClass( name, classMap )
602                                  : targetClass;
603                         }
604                         Object newRoot = jsonConfig.getNewBeanInstanceStrategy()
605                               .newInstance( targetClass, null );
606                         setProperty( root, key, toBean( (JSONObject) value, newRoot, jsonConfig ),
607                               jsonConfig );
608                      }
609                   }else if( root instanceof Map ){
610                      Class targetClass = findTargetClass( key, classMap );
611                      targetClass = targetClass == null ? findTargetClass( name, classMap )
612                            : targetClass;
613                      Object newRoot = jsonConfig.getNewBeanInstanceStrategy()
614                            .newInstance( targetClass, null );
615                      setProperty( root, key, toBean( (JSONObject) value, newRoot, jsonConfig ),
616                            jsonConfig );
617                   }else{
618                      log.warn( "Tried to assign property " + key + ":" + type.getName()
619                            + " to bean of class " + rootClass.getName() );
620                   }
621                }
622             }else{
623                if( type.isPrimitive() ){
624                   // assume assigned default value
625                   log.warn( "Tried to assign null value to " + key + ":" + type.getName() );
626                   setProperty( root, key, JSONUtils.getMorpherRegistry()
627                         .morph( type, null ), jsonConfig );
628                }else{
629                   setProperty( root, key, null, jsonConfig );
630                }
631             }
632          }catch( JSONException jsone ){
633             throw jsone;
634          }catch( Exception e ){
635             throw new JSONException( "Error while setting property=" + name + " type " + type, e );
636          }
637       }
638 
639       return root;
640    }
641 
642    /**
643     * Creates a JSONObject from a POJO.<br>
644     * Supports nested maps, POJOs, and arrays/collections.
645     *
646     * @param bean An object with POJO conventions
647     * @throws JSONException if the bean can not be converted to a proper
648     *         JSONObject.
649     */
650    private static JSONObject _fromBean( Object bean, JsonConfig jsonConfig ) {
651       if( !addInstance( bean ) ){
652          try{
653             return jsonConfig.getCycleDetectionStrategy()
654                   .handleRepeatedReferenceAsObject( bean );
655          }catch( JSONException jsone ){
656             removeInstance( bean );
657             fireErrorEvent( jsone, jsonConfig );
658             throw jsone;
659          }catch( RuntimeException e ){
660             removeInstance( bean );
661             JSONException jsone = new JSONException( e );
662             fireErrorEvent( jsone, jsonConfig );
663             throw jsone;
664          }
665       }
666       fireObjectStartEvent( jsonConfig );
667 
668       JsonBeanProcessor processor = jsonConfig.findJsonBeanProcessor( bean.getClass() );
669       if( processor != null ){
670          JSONObject json = null;
671          try{
672             json = processor.processBean( bean, jsonConfig );
673             if( json == null ){
674                json = (JSONObject) jsonConfig.findDefaultValueProcessor( bean.getClass() )
675                      .getDefaultValue( bean.getClass() );
676                if( json == null ){
677                   json = new JSONObject( true );
678                }
679             }
680             removeInstance( bean );
681             fireObjectEndEvent( jsonConfig );
682          }catch( JSONException jsone ){
683             removeInstance( bean );
684             fireErrorEvent( jsone, jsonConfig );
685             throw jsone;
686          }catch( RuntimeException e ){
687             removeInstance( bean );
688             JSONException jsone = new JSONException( e );
689             fireErrorEvent( jsone, jsonConfig );
690             throw jsone;
691          }
692          return json;
693       }
694 
695       Class beanClass = bean.getClass();
696       PropertyNameProcessor propertyNameProcessor = jsonConfig.findJsonPropertyNameProcessor( beanClass );      
697       Collection exclusions = jsonConfig.getMergedExcludes( beanClass );
698       JSONObject jsonObject = new JSONObject();
699       try{
700          PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors( bean );
701          PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
702          for( int i = 0; i < pds.length; i++ ){
703             boolean bypass = false;
704             String key = pds[i].getName();
705             if( exclusions.contains( key ) ){
706                continue;
707             }
708 
709             if( jsonConfig.isIgnoreTransientFields() && isTransientField( key, beanClass ) ){
710                continue;
711             }
712 
713             Class type = pds[i].getPropertyType();
714             try { pds[i].getReadMethod(); }
715             catch( Exception e ) {
716                // bug 2565295
717                String warning = "Property '" + key + "' of "+ beanClass+" has no read method. SKIPPED";
718                fireWarnEvent( warning, jsonConfig );
719                log.info( warning );
720                continue;
721             }
722             if( pds[i].getReadMethod() != null ){
723                Object value = PropertyUtils.getProperty( bean, key );
724                if( jsonPropertyFilter != null && jsonPropertyFilter.apply( bean, key, value ) ){
725                   continue;
726                }
727                JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor(
728                      beanClass, type, key );
729                if( jsonValueProcessor != null ){
730                   value = jsonValueProcessor.processObjectValue( key, value, jsonConfig );
731                   bypass = true;
732                   if( !JsonVerifier.isValidJsonValue( value ) ){
733                      throw new JSONException( "Value is not a valid JSON value. " + value );
734                   }
735                }
736                if( propertyNameProcessor != null ){
737                   key = propertyNameProcessor.processPropertyName( beanClass, key );
738                }
739                setValue( jsonObject, key, value, type, jsonConfig, bypass );
740             }else{
741                String warning = "Property '" + key + "' of "+ beanClass+" has no read method. SKIPPED";
742                fireWarnEvent( warning, jsonConfig );
743                log.info( warning );
744             }
745          }
746          // inspect public fields, this operation may fail under
747          // a SecurityManager so we will eat all exceptions
748          try {
749             if( !jsonConfig.isIgnorePublicFields() ) {
750                Field[] fields = beanClass.getFields();
751                for( int i = 0; i < fields.length; i++ ) {
752                   boolean bypass = false;
753                   Field field = fields[i];
754                   String key = field.getName();
755                   if( exclusions.contains( key ) ) {
756                      continue;
757                   }
758 
759                   if( jsonConfig.isIgnoreTransientFields() && isTransientField( field ) ) {
760                      continue;
761                   }
762 
763                   Class type = field.getType();
764                   Object value = field.get( bean );
765                   if( jsonPropertyFilter != null && jsonPropertyFilter.apply( bean, key, value ) ) {
766                      continue;
767                   }
768                   JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor( beanClass, type, key );
769                   if( jsonValueProcessor != null ) {
770                      value = jsonValueProcessor.processObjectValue( key, value, jsonConfig );
771                      bypass = true;
772                      if( !JsonVerifier.isValidJsonValue( value ) ) {
773                         throw new JSONException( "Value is not a valid JSON value. " + value );
774                      }
775                   }
776                   if( propertyNameProcessor != null ) {
777                      key = propertyNameProcessor.processPropertyName( beanClass, key );
778                   }
779                   setValue( jsonObject, key, value, type, jsonConfig, bypass );
780                }
781             }
782          }
783          catch( Exception e ){
784             log.trace( "Couldn't read public fields.", e );
785          }
786       }catch( JSONException jsone ){
787          removeInstance( bean );
788          fireErrorEvent( jsone, jsonConfig );
789          throw jsone;
790       }catch( Exception e ){
791          removeInstance( bean );
792          JSONException jsone = new JSONException( e );
793          fireErrorEvent( jsone, jsonConfig );
794          throw jsone;
795       }
796 
797       removeInstance( bean );
798       fireObjectEndEvent( jsonConfig );
799       return jsonObject;
800    }
801 
802    private static JSONObject _fromDynaBean( DynaBean bean, JsonConfig jsonConfig ) {
803       if( bean == null ){
804          fireObjectStartEvent( jsonConfig );
805          fireObjectEndEvent( jsonConfig );
806          return new JSONObject( true );
807       }
808 
809       if( !addInstance( bean ) ){
810          try{
811             return jsonConfig.getCycleDetectionStrategy()
812                   .handleRepeatedReferenceAsObject( bean );
813          }catch( JSONException jsone ){
814             removeInstance( bean );
815             fireErrorEvent( jsone, jsonConfig );
816             throw jsone;
817          }catch( RuntimeException e ){
818             removeInstance( bean );
819             JSONException jsone = new JSONException( e );
820             fireErrorEvent( jsone, jsonConfig );
821             throw jsone;
822          }
823       }
824       fireObjectStartEvent( jsonConfig );
825 
826       JSONObject jsonObject = new JSONObject();
827       try{
828          DynaProperty[] props = bean.getDynaClass()
829                .getDynaProperties();
830          Collection exclusions = jsonConfig.getMergedExcludes();
831          PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
832          for( int i = 0; i < props.length; i++ ){
833             boolean bypass = false;
834             DynaProperty dynaProperty = props[i];
835             String key = dynaProperty.getName();
836             if( exclusions.contains( key ) ){
837                continue;
838             }
839             Class type = dynaProperty.getType();
840             Object value = bean.get( dynaProperty.getName() );
841             if( jsonPropertyFilter != null && jsonPropertyFilter.apply( bean, key, value ) ){
842                continue;
843             }
844             JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor( type, key );
845             if( jsonValueProcessor != null ){
846                value = jsonValueProcessor.processObjectValue( key, value, jsonConfig );
847                bypass = true;
848                if( !JsonVerifier.isValidJsonValue( value ) ){
849                   throw new JSONException( "Value is not a valid JSON value. " + value );
850                }
851             }
852             setValue( jsonObject, key, value, type, jsonConfig, bypass );
853          }
854       }catch( JSONException jsone ){
855          removeInstance( bean );
856          fireErrorEvent( jsone, jsonConfig );
857          throw jsone;
858       }catch( RuntimeException e ){
859          removeInstance( bean );
860          JSONException jsone = new JSONException( e );
861          fireErrorEvent( jsone, jsonConfig );
862          throw jsone;
863       }
864 
865       removeInstance( bean );
866       fireObjectEndEvent( jsonConfig );
867       return jsonObject;
868    }
869 
870    private static JSONObject _fromJSONObject( JSONObject object, JsonConfig jsonConfig ) {
871       if( object == null || object.isNullObject() ){
872          fireObjectStartEvent( jsonConfig );
873          fireObjectEndEvent( jsonConfig );
874          return new JSONObject( true );
875       }
876 
877       if( !addInstance( object ) ){
878          try{
879             return jsonConfig.getCycleDetectionStrategy()
880                   .handleRepeatedReferenceAsObject( object );
881          }catch( JSONException jsone ){
882             removeInstance( object );
883             fireErrorEvent( jsone, jsonConfig );
884             throw jsone;
885          }catch( RuntimeException e ){
886             removeInstance( object );
887             JSONException jsone = new JSONException( e );
888             fireErrorEvent( jsone, jsonConfig );
889             throw jsone;
890          }
891       }
892       fireObjectStartEvent( jsonConfig );
893 
894       JSONArray sa = object.names(jsonConfig);
895       Collection exclusions = jsonConfig.getMergedExcludes();
896       JSONObject jsonObject = new JSONObject();
897       PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
898       for( Iterator i = sa.iterator(); i.hasNext(); ){
899          Object k =  i.next();
900          if( k == null ){
901             throw new JSONException("JSON keys cannot be null.");
902          }
903          if( !(k instanceof String) && !jsonConfig.isAllowNonStringKeys()) {
904             throw new ClassCastException("JSON keys must be strings.");
905          }
906          String key = String.valueOf( k );
907          if( "null".equals( key )){
908             throw new NullPointerException("JSON keys must not be null nor the 'null' string.");
909          }
910          if( exclusions.contains( key ) ){
911             continue;
912          }
913          Object value = object.opt( key );
914          if( jsonPropertyFilter != null && jsonPropertyFilter.apply( object, key, value ) ){
915             continue;
916          }
917          if( jsonObject.properties.containsKey( key ) ){
918             jsonObject.accumulate( key, value, jsonConfig );
919             firePropertySetEvent( key, value, true, jsonConfig );
920          }else{
921             jsonObject.setInternal( key, value, jsonConfig );
922             firePropertySetEvent( key, value, false, jsonConfig );
923          }
924       }
925 
926       removeInstance( object );
927       fireObjectEndEvent( jsonConfig );
928       return jsonObject;
929    }
930 
931    private static JSONObject _fromJSONString( JSONString string, JsonConfig jsonConfig ) {
932       return _fromJSONTokener( new JSONTokener( string.toJSONString() ), jsonConfig );
933    }
934 
935    private static JSONObject _fromJSONTokener( JSONTokener tokener, JsonConfig jsonConfig ) {
936 
937       try{
938          char c;
939          String key;
940          Object value;
941 
942          if( tokener.matches( "null.*" ) ){
943             fireObjectStartEvent( jsonConfig );
944             fireObjectEndEvent( jsonConfig );
945             return new JSONObject( true );
946          }
947 
948          if( tokener.nextClean() != '{' ){
949             throw tokener.syntaxError( "A JSONObject text must begin with '{'" );
950          }
951          fireObjectStartEvent( jsonConfig );
952 
953          Collection exclusions = jsonConfig.getMergedExcludes();
954          PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
955          JSONObject jsonObject = new JSONObject();
956          for( ;; ){
957             c = tokener.nextClean();
958             switch( c ){
959                case 0:
960                   throw tokener.syntaxError( "A JSONObject text must end with '}'" );
961                case '}':
962                   fireObjectEndEvent( jsonConfig );
963                   return jsonObject;
964                default:
965                   tokener.back();
966                   key = tokener.nextValue( jsonConfig )
967                         .toString();
968             }
969 
970             /*
971              * The key is followed by ':'. We will also tolerate '=' or '=>'.
972              */
973 
974             c = tokener.nextClean();
975             if( c == '=' ){
976                if( tokener.next() != '>' ){
977                   tokener.back();
978                }
979             }else if( c != ':' ){
980                throw tokener.syntaxError( "Expected a ':' after a key" );
981             }
982 
983             char peek = tokener.peek();
984             boolean quoted = peek == '"' || peek == '\'';
985             Object v = tokener.nextValue( jsonConfig );
986             if( quoted || !JSONUtils.isFunctionHeader( v ) ){
987                if( exclusions.contains( key ) ){
988                   switch( tokener.nextClean() ){
989                      case ';':
990                      case ',':
991                         if( tokener.nextClean() == '}' ){
992                            fireObjectEndEvent( jsonConfig );
993                            return jsonObject;
994                         }
995                         tokener.back();
996                         break;
997                      case '}':
998                         fireObjectEndEvent( jsonConfig );
999                         return jsonObject;
1000                      default:
1001                         throw tokener.syntaxError( "Expected a ',' or '}'" );
1002                   }
1003                   continue;
1004                }
1005                if( jsonPropertyFilter == null || !jsonPropertyFilter.apply( tokener, key, v ) ){
1006                   if( quoted && v instanceof String && (JSONUtils.mayBeJSON( (String) v ) || JSONUtils.isFunction( v ))){
1007                      v = JSONUtils.DOUBLE_QUOTE + v + JSONUtils.DOUBLE_QUOTE;
1008                   }
1009                   if( jsonObject.properties.containsKey( key ) ){
1010                      jsonObject.accumulate( key, v, jsonConfig );
1011                      firePropertySetEvent( key, v, true, jsonConfig );
1012                   }else{
1013                      jsonObject.element( key, v, jsonConfig );
1014                      firePropertySetEvent( key, v, false, jsonConfig );
1015                   }
1016                }
1017             }else{
1018                // read params if any
1019                String params = JSONUtils.getFunctionParams( (String) v );
1020                // read function text
1021                int i = 0;
1022                StringBuffer sb = new StringBuffer();
1023                for( ;; ){
1024                   char ch = tokener.next();
1025                   if( ch == 0 ){
1026                      break;
1027                   }
1028                   if( ch == '{' ){
1029                      i++;
1030                   }
1031                   if( ch == '}' ){
1032                      i--;
1033                   }
1034                   sb.append( ch );
1035                   if( i == 0 ){
1036                      break;
1037                   }
1038                }
1039                if( i != 0 ){
1040                   throw tokener.syntaxError( "Unbalanced '{' or '}' on prop: " + v );
1041                }
1042                // trim '{' at start and '}' at end
1043                String text = sb.toString();
1044                text = text.substring( 1, text.length() - 1 )
1045                      .trim();
1046                value = new JSONFunction(
1047                      (params != null) ? StringUtils.split( params, "," ) : null, text );
1048                if( jsonPropertyFilter == null || !jsonPropertyFilter.apply( tokener, key, value ) ){
1049                   if( jsonObject.properties.containsKey( key ) ){
1050                      jsonObject.accumulate( key, value, jsonConfig );
1051                      firePropertySetEvent( key, value, true, jsonConfig );
1052                   }else{
1053                      jsonObject.element( key, value, jsonConfig );
1054                      firePropertySetEvent( key, value, false, jsonConfig );
1055                   }
1056                }
1057             }
1058 
1059             /*
1060              * Pairs are separated by ','. We will also tolerate ';'.
1061              */
1062 
1063             switch( tokener.nextClean() ){
1064                case ';':
1065                case ',':
1066                   if( tokener.nextClean() == '}' ){
1067                      fireObjectEndEvent( jsonConfig );
1068                      return jsonObject;
1069                   }
1070                   tokener.back();
1071                   break;
1072                case '}':
1073                   fireObjectEndEvent( jsonConfig );
1074                   return jsonObject;
1075                default:
1076                   throw tokener.syntaxError( "Expected a ',' or '}'" );
1077             }
1078          }
1079       }catch( JSONException jsone ){
1080          fireErrorEvent( jsone, jsonConfig );
1081          throw jsone;
1082       }
1083    }
1084 
1085    private static JSONObject _fromMap( Map map, JsonConfig jsonConfig ) {
1086       if( map == null ){
1087          fireObjectStartEvent( jsonConfig );
1088          fireObjectEndEvent( jsonConfig );
1089          return new JSONObject( true );
1090       }
1091 
1092       if( !addInstance( map ) ){
1093          try{
1094             return jsonConfig.getCycleDetectionStrategy()
1095                   .handleRepeatedReferenceAsObject( map );
1096          }catch( JSONException jsone ){
1097             removeInstance( map );
1098             fireErrorEvent( jsone, jsonConfig );
1099             throw jsone;
1100          }catch( RuntimeException e ){
1101             removeInstance( map );
1102             JSONException jsone = new JSONException( e );
1103             fireErrorEvent( jsone, jsonConfig );
1104             throw jsone;
1105          }
1106       }
1107       fireObjectStartEvent( jsonConfig );
1108 
1109       Collection exclusions = jsonConfig.getMergedExcludes();
1110       JSONObject jsonObject = new JSONObject();
1111       PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter();
1112       try{
1113          for( Iterator entries = map.entrySet()
1114                .iterator(); entries.hasNext(); ){
1115             boolean bypass = false;
1116             Map.Entry entry = (Map.Entry) entries.next();
1117             Object k = entry.getKey();
1118             if( k == null ){
1119                throw new JSONException("JSON keys cannot be null.");
1120             }
1121             if( !(k instanceof String) && !jsonConfig.isAllowNonStringKeys() ) {
1122                throw new ClassCastException("JSON keys must be strings.");
1123             }
1124             String key = String.valueOf( k );
1125             if( "null".equals( key )){
1126                throw new NullPointerException("JSON keys must not be null nor the 'null' string.");
1127             }
1128             if( exclusions.contains( key ) ){
1129                continue;
1130             }
1131             Object value = entry.getValue();
1132             if( jsonPropertyFilter != null && jsonPropertyFilter.apply( map, key, value ) ){
1133                continue;
1134             }
1135             if( value != null ){
1136                JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor(
1137                      value.getClass(), key );
1138                if( jsonValueProcessor != null ){
1139                   value = jsonValueProcessor.processObjectValue( key, value, jsonConfig );
1140                   bypass = true;
1141                   if( !JsonVerifier.isValidJsonValue( value ) ){
1142                      throw new JSONException( "Value is not a valid JSON value. " + value );
1143                   }
1144                }
1145                setValue( jsonObject, key, value, value.getClass(), jsonConfig, bypass );
1146             }else{
1147                if( jsonObject.properties.containsKey( key ) ){
1148                   jsonObject.accumulate( key, JSONNull.getInstance() );
1149                   firePropertySetEvent( key, JSONNull.getInstance(), true, jsonConfig );
1150                }else{
1151                   jsonObject.element( key, JSONNull.getInstance() );
1152                   firePropertySetEvent( key, JSONNull.getInstance(), false, jsonConfig );
1153                }
1154             }
1155          }
1156       }catch( JSONException jsone ){
1157          removeInstance( map );
1158          fireErrorEvent( jsone, jsonConfig );
1159          throw jsone;
1160       }catch( RuntimeException e ){
1161          removeInstance( map );
1162          JSONException jsone = new JSONException( e );
1163          fireErrorEvent( jsone, jsonConfig );
1164          throw jsone;
1165       }
1166 
1167       removeInstance( map );
1168       fireObjectEndEvent( jsonConfig );
1169       return jsonObject;
1170    }
1171 
1172    private static JSONObject _fromString( String str, JsonConfig jsonConfig ) {
1173       if( str == null || "null".equals( str ) ){
1174          fireObjectStartEvent( jsonConfig );
1175          fireObjectEndEvent( jsonConfig );
1176          return new JSONObject( true );
1177       }
1178       return _fromJSONTokener( new JSONTokener( str ), jsonConfig );
1179    }
1180 
1181    private static Object convertPropertyValueToArray( String key, Object value, Class targetType,
1182          JsonConfig jsonConfig, Map classMap ) {
1183       Class innerType = JSONUtils.getInnerComponentType( targetType );
1184       Class targetInnerType = findTargetClass( key, classMap );
1185       if( innerType.equals( Object.class ) && targetInnerType != null
1186             && !targetInnerType.equals( Object.class ) ){
1187          innerType = targetInnerType;
1188       }
1189       JsonConfig jsc = jsonConfig.copy();
1190       jsc.setRootClass( innerType );
1191       jsc.setClassMap( classMap );
1192       Object array = JSONArray.toArray( (JSONArray) value, jsc );
1193       if( innerType.isPrimitive() || JSONUtils.isNumber( innerType )
1194             || Boolean.class.isAssignableFrom( innerType ) || JSONUtils.isString( innerType ) ){
1195          array = JSONUtils.getMorpherRegistry()
1196                .morph( Array.newInstance( innerType, 0 )
1197                      .getClass(), array );
1198       }else if( !array.getClass()
1199             .equals( targetType ) ){
1200          if( !targetType.equals( Object.class ) ){
1201             Morpher morpher = JSONUtils.getMorpherRegistry()
1202                   .getMorpherFor( Array.newInstance( innerType, 0 )
1203                         .getClass() );
1204             if( IdentityObjectMorpher.getInstance()
1205                   .equals( morpher ) ){
1206                ObjectArrayMorpher beanMorpher = new ObjectArrayMorpher( new BeanMorpher( innerType,
1207                      JSONUtils.getMorpherRegistry() ) );
1208                JSONUtils.getMorpherRegistry()
1209                      .registerMorpher( beanMorpher );
1210             }
1211             array = JSONUtils.getMorpherRegistry()
1212                   .morph( Array.newInstance( innerType, 0 )
1213                         .getClass(), array );
1214          }
1215       }
1216       return array;
1217    }
1218 
1219    private static List convertPropertyValueToList( String key, Object value, JsonConfig jsonConfig,
1220          String name, Map classMap ) {
1221       Class targetClass = findTargetClass( key, classMap );
1222       targetClass = targetClass == null ? findTargetClass( name, classMap ) : targetClass;
1223       JsonConfig jsc = jsonConfig.copy();
1224       jsc.setRootClass( targetClass );
1225       jsc.setClassMap( classMap );
1226       List list = (List) JSONArray.toCollection( (JSONArray) value, jsc );
1227       return list;
1228    }
1229 
1230    private static Collection convertPropertyValueToCollection( String key, Object value, JsonConfig jsonConfig,
1231          String name, Map classMap, Class collectionType ) {
1232       Class targetClass = findTargetClass( key, classMap );
1233       targetClass = targetClass == null ? findTargetClass( name, classMap ) : targetClass;
1234       JsonConfig jsc = jsonConfig.copy();
1235       jsc.setRootClass( targetClass );
1236       jsc.setClassMap( classMap );
1237       jsc.setCollectionType( collectionType );
1238       return JSONArray.toCollection( (JSONArray) value, jsc );
1239    }
1240 
1241    /**
1242     * Locates a Class associated to a specifi key.<br>
1243     * The key may be a regexp.
1244     */
1245    private static Class findTargetClass( String key, Map classMap ) {
1246       // try get first
1247       Class targetClass = (Class) classMap.get( key );
1248       if( targetClass == null ){
1249          // try with regexp
1250          // this will hit performance as it must iterate over all the keys
1251          // and create a RegexpMatcher for each key
1252          for( Iterator i = classMap.entrySet()
1253                .iterator(); i.hasNext(); ){
1254             Map.Entry entry = (Map.Entry) i.next();
1255             if( RegexpUtils.getMatcher( (String) entry.getKey() )
1256                   .matches( key ) ){
1257                targetClass = (Class) entry.getValue();
1258                break;
1259             }
1260          }
1261       }
1262 
1263       return targetClass;
1264    }
1265 
1266    private static boolean isTransientField( String name, Class beanClass ) {
1267       try{
1268          return isTransientField(beanClass.getDeclaredField( name ));
1269       }catch( Exception e ){
1270          // swallow exception
1271       }
1272       return false;
1273    }
1274    
1275    private static boolean isTransientField( Field field ) {
1276       return (field.getModifiers() & Modifier.TRANSIENT) == Modifier.TRANSIENT;
1277    }
1278 
1279    private static Object morphPropertyValue( String key, Object value, Class type, Class targetType ) {
1280       Morpher morpher = JSONUtils.getMorpherRegistry()
1281             .getMorpherFor( targetType );
1282       if( IdentityObjectMorpher.getInstance()
1283             .equals( morpher ) ){
1284          log.warn( "Can't transform property '" + key + "' from " + type.getName() + " into "
1285                + targetType.getName() + ". Will register a default BeanMorpher" );
1286          JSONUtils.getMorpherRegistry()
1287                .registerMorpher( new BeanMorpher( targetType, JSONUtils.getMorpherRegistry() ) );
1288       }
1289       value = JSONUtils.getMorpherRegistry()
1290             .morph( targetType, value );
1291       return value;
1292    }
1293 
1294    /**
1295     * Sets a property on the target bean.<br>
1296     * Bean may be a Map or a POJO.
1297     */
1298    private static void setProperty( Object bean, String key, Object value, JsonConfig jsonConfig )
1299          throws Exception {
1300       PropertySetStrategy propertySetStrategy = jsonConfig.getPropertySetStrategy() != null ? jsonConfig.getPropertySetStrategy()
1301             : PropertySetStrategy.DEFAULT;
1302       propertySetStrategy.setProperty( bean, key, value, jsonConfig );
1303    }
1304    
1305    private static void setValue( JSONObject jsonObject, String key, Object value, Class type,
1306          JsonConfig jsonConfig, boolean bypass ) {
1307       boolean accumulated = false;
1308       if( value == null ){
1309          value = jsonConfig.findDefaultValueProcessor( type )
1310                .getDefaultValue( type );
1311          if( !JsonVerifier.isValidJsonValue( value ) ){
1312             throw new JSONException( "Value is not a valid JSON value. " + value );
1313          }
1314       }
1315       if( jsonObject.properties.containsKey( key ) ){
1316          if( String.class.isAssignableFrom( type ) ){
1317             Object o = jsonObject.opt( key );
1318             if( o instanceof JSONArray ){
1319                ((JSONArray) o).addString( (String) value );
1320             }else{
1321                jsonObject.properties.put( key, new JSONArray().element( o )
1322                      .addString( (String) value ) );
1323             }
1324          }else{
1325             jsonObject.accumulate( key, value, jsonConfig );
1326          }
1327          accumulated = true;
1328       }else{
1329          if( bypass || String.class.isAssignableFrom( type ) ){
1330             jsonObject.properties.put( key, value );
1331          }else{
1332             jsonObject.setInternal( key, value, jsonConfig );
1333          }
1334       }
1335 
1336       value = jsonObject.opt( key );
1337       if( accumulated ){
1338          JSONArray array = (JSONArray) value;
1339          value = array.get( array.size() - 1 );
1340       }
1341       firePropertySetEvent( key, value, accumulated, jsonConfig );
1342    }
1343 
1344    // ------------------------------------------------------
1345 
1346    /** identifies this object as null */
1347    private boolean nullObject;
1348 
1349    /**
1350     * The Map where the JSONObject's properties are kept.
1351     */
1352    private Map properties;
1353 
1354    /**
1355     * Construct an empty JSONObject.
1356     */
1357    public JSONObject() {
1358       this.properties = new ListOrderedMap();
1359    }
1360 
1361    /**
1362     * Creates a JSONObject that is null.
1363     */
1364    public JSONObject( boolean isNull ) {
1365       this();
1366       this.nullObject = isNull;
1367    }
1368 
1369    /**
1370     * Accumulate values under a key. It is similar to the element method except
1371     * that if there is already an object stored under the key then a JSONArray
1372     * is stored under the key to hold all of the accumulated values. If there is
1373     * already a JSONArray, then the new value is appended to it. In contrast,
1374     * the replace method replaces the previous value.
1375     *
1376     * @param key A key string.
1377     * @param value An object to be accumulated under the key.
1378     * @return this.
1379     * @throws JSONException If the value is an invalid number or if the key is
1380     *         null.
1381     */
1382    public JSONObject accumulate( String key, boolean value ) {
1383       return _accumulate( key, value ? Boolean.TRUE : Boolean.FALSE, new JsonConfig() );
1384    }
1385 
1386    /**
1387     * Accumulate values under a key. It is similar to the element method except
1388     * that if there is already an object stored under the key then a JSONArray
1389     * is stored under the key to hold all of the accumulated values. If there is
1390     * already a JSONArray, then the new value is appended to it. In contrast,
1391     * the replace method replaces the previous value.
1392     *
1393     * @param key A key string.
1394     * @param value An object to be accumulated under the key.
1395     * @return this.
1396     * @throws JSONException If the value is an invalid number or if the key is
1397     *         null.
1398     */
1399    public JSONObject accumulate( String key, double value ) {
1400       return _accumulate( key, new Double( value ), new JsonConfig() );
1401    }
1402 
1403    /**
1404     * Accumulate values under a key. It is similar to the element method except
1405     * that if there is already an object stored under the key then a JSONArray
1406     * is stored under the key to hold all of the accumulated values. If there is
1407     * already a JSONArray, then the new value is appended to it. In contrast,
1408     * the replace method replaces the previous value.
1409     *
1410     * @param key A key string.
1411     * @param value An object to be accumulated under the key.
1412     * @return this.
1413     * @throws JSONException If the value is an invalid number or if the key is
1414     *         null.
1415     */
1416    public JSONObject accumulate( String key, int value ) {
1417       return _accumulate( key, new Integer( value ), new JsonConfig() );
1418    }
1419 
1420    /**
1421     * Accumulate values under a key. It is similar to the element method except
1422     * that if there is already an object stored under the key then a JSONArray
1423     * is stored under the key to hold all of the accumulated values. If there is
1424     * already a JSONArray, then the new value is appended to it. In contrast,
1425     * the replace method replaces the previous value.
1426     *
1427     * @param key A key string.
1428     * @param value An object to be accumulated under the key.
1429     * @return this.
1430     * @throws JSONException If the value is an invalid number or if the key is
1431     *         null.
1432     */
1433    public JSONObject accumulate( String key, long value ) {
1434       return _accumulate( key, new Long( value ), new JsonConfig() );
1435    }
1436 
1437    /**
1438     * Accumulate values under a key. It is similar to the element method except
1439     * that if there is already an object stored under the key then a JSONArray
1440     * is stored under the key to hold all of the accumulated values. If there is
1441     * already a JSONArray, then the new value is appended to it. In contrast,
1442     * the replace method replaces the previous value.
1443     *
1444     * @param key A key string.
1445     * @param value An object to be accumulated under the key.
1446     * @return this.
1447     * @throws JSONException If the value is an invalid number or if the key is
1448     *         null.
1449     */
1450    public JSONObject accumulate( String key, Object value ) {
1451       return _accumulate( key, value, new JsonConfig() );
1452    }
1453 
1454    /**
1455     * Accumulate values under a key. It is similar to the element method except
1456     * that if there is already an object stored under the key then a JSONArray
1457     * is stored under the key to hold all of the accumulated values. If there is
1458     * already a JSONArray, then the new value is appended to it. In contrast,
1459     * the replace method replaces the previous value.
1460     *
1461     * @param key A key string.
1462     * @param value An object to be accumulated under the key.
1463     * @return this.
1464     * @throws JSONException If the value is an invalid number or if the key is
1465     *         null.
1466     */
1467    public JSONObject accumulate( String key, Object value, JsonConfig jsonConfig ) {
1468       return _accumulate( key, value, jsonConfig );
1469    }
1470 
1471    public void accumulateAll( Map map ) {
1472       accumulateAll( map, new JsonConfig() );
1473    }
1474 
1475    public void accumulateAll( Map map, JsonConfig jsonConfig ) {
1476       if( map instanceof JSONObject ){
1477          for( Iterator entries = map.entrySet()
1478                .iterator(); entries.hasNext(); ){
1479             Map.Entry entry = (Map.Entry) entries.next();
1480             String key = (String) entry.getKey();
1481             Object value = entry.getValue();
1482             accumulate( key, value, jsonConfig );
1483          }
1484       }else{
1485          for( Iterator entries = map.entrySet()
1486                .iterator(); entries.hasNext(); ){
1487             Map.Entry entry = (Map.Entry) entries.next();
1488             String key = String.valueOf( entry.getKey() );
1489             Object value = entry.getValue();
1490             accumulate( key, value, jsonConfig );
1491          }
1492       }
1493    }
1494 
1495    public void clear() {
1496       properties.clear();
1497    }
1498 
1499    public int compareTo( Object obj ) {
1500       if( obj != null && (obj instanceof JSONObject) ){
1501          JSONObject other = (JSONObject) obj;
1502          int size1 = size();
1503          int size2 = other.size();
1504          if( size1 < size2 ){
1505             return -1;
1506          }else if( size1 > size2 ){
1507             return 1;
1508          }else if( this.equals( other ) ){
1509             return 0;
1510          }
1511       }
1512       return -1;
1513    }
1514 
1515    public boolean containsKey( Object key ) {
1516       return properties.containsKey( key );
1517    }
1518 
1519    public boolean containsValue( Object value ) {
1520       return containsValue( value, new JsonConfig() );
1521    }
1522 
1523    public boolean containsValue( Object value, JsonConfig jsonConfig ) {
1524       try{
1525          value = processValue( value, jsonConfig );
1526       }catch( JSONException e ){
1527          return false;
1528       }
1529       return properties.containsValue( value );
1530    }
1531 
1532    /**
1533     * Remove a name and its value, if present.
1534     *
1535     * @param key A key string.
1536     * @return this.
1537     */
1538    public JSONObject discard( String key ) {
1539       verifyIsNull();
1540       this.properties.remove( key );
1541       return this;
1542    }
1543 
1544    /**
1545     * Put a key/boolean pair in the JSONObject.
1546     *
1547     * @param key A key string.
1548     * @param value A boolean which is the value.
1549     * @return this.
1550     * @throws JSONException If the key is null.
1551     */
1552    public JSONObject element( String key, boolean value ) {
1553       verifyIsNull();
1554       return element( key, value ? Boolean.TRUE : Boolean.FALSE );
1555    }
1556 
1557    /**
1558     * Put a key/value pair in the JSONObject, where the value will be a
1559     * JSONArray which is produced from a Collection.
1560     *
1561     * @param key A key string.
1562     * @param value A Collection value.
1563     * @return this.
1564     * @throws JSONException
1565     */
1566    public JSONObject element( String key, Collection value ) {
1567       return element( key, value, new JsonConfig() );
1568    }
1569 
1570    /**
1571     * Put a key/value pair in the JSONObject, where the value will be a
1572     * JSONArray which is produced from a Collection.
1573     *
1574     * @param key A key string.
1575     * @param value A Collection value.
1576     * @return this.
1577     * @throws JSONException
1578     */
1579    public JSONObject element( String key, Collection value, JsonConfig jsonConfig ) {
1580       verifyIsNull();
1581       if( !(value instanceof JSONArray) ){
1582          value = JSONArray.fromObject( value, jsonConfig );
1583       }
1584       return setInternal( key, value, jsonConfig );
1585    }
1586 
1587    /**
1588     * Put a key/double pair in the JSONObject.
1589     *
1590     * @param key A key string.
1591     * @param value A double which is the value.
1592     * @return this.
1593     * @throws JSONException If the key is null or if the number is invalid.
1594     */
1595    public JSONObject element( String key, double value ) {
1596       verifyIsNull();
1597       Double d = new Double( value );
1598       JSONUtils.testValidity( d );
1599       return element( key, d );
1600    }
1601 
1602    /**
1603     * Put a key/int pair in the JSONObject.
1604     *
1605     * @param key A key string.
1606     * @param value An int which is the value.
1607     * @return this.
1608     * @throws JSONException If the key is null.
1609     */
1610    public JSONObject element( String key, int value ) {
1611       verifyIsNull();
1612       return element( key, new Integer( value ) );
1613    }
1614 
1615    /**
1616     * Put a key/long pair in the JSONObject.
1617     *
1618     * @param key A key string.
1619     * @param value A long which is the value.
1620     * @return this.
1621     * @throws JSONException If the key is null.
1622     */
1623    public JSONObject element( String key, long value ) {
1624       verifyIsNull();
1625       return element( key, new Long( value ) );
1626    }
1627 
1628    /**
1629     * Put a key/value pair in the JSONObject, where the value will be a
1630     * JSONObject which is produced from a Map.
1631     *
1632     * @param key A key string.
1633     * @param value A Map value.
1634     * @return this.
1635     * @throws JSONException
1636     */
1637    public JSONObject element( String key, Map value ) {
1638       return element( key, value, new JsonConfig() );
1639    }
1640 
1641    /**
1642     * Put a key/value pair in the JSONObject, where the value will be a
1643     * JSONObject which is produced from a Map.
1644     *
1645     * @param key A key string.
1646     * @param value A Map value.
1647     * @return this.
1648     * @throws JSONException
1649     */
1650    public JSONObject element( String key, Map value, JsonConfig jsonConfig ) {
1651       verifyIsNull();
1652       if( value instanceof JSONObject ){
1653          return setInternal( key, value, jsonConfig );
1654       }else{
1655          return element( key, JSONObject.fromObject( value, jsonConfig ), jsonConfig );
1656       }
1657    }
1658 
1659    /**
1660     * Put a key/value pair in the JSONObject. If the value is null, then the key
1661     * will be removed from the JSONObject if it is present.<br>
1662     * If there is a previous value assigned to the key, it will call accumulate.
1663     *
1664     * @param key A key string.
1665     * @param value An object which is the value. It should be of one of these
1666     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
1667     *        String, or the JSONNull object.
1668     * @return this.
1669     * @throws JSONException If the value is non-finite number or if the key is
1670     *         null.
1671     */
1672    public JSONObject element( String key, Object value ) {
1673       return element( key, value, new JsonConfig() );
1674    }
1675 
1676    /**
1677     * Put a key/value pair in the JSONObject. If the value is null, then the key
1678     * will be removed from the JSONObject if it is present.<br>
1679     * If there is a previous value assigned to the key, it will call accumulate.
1680     *
1681     * @param key A key string.
1682     * @param value An object which is the value. It should be of one of these
1683     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
1684     *        String, or the JSONNull object.
1685     * @return this.
1686     * @throws JSONException If the value is non-finite number or if the key is
1687     *         null.
1688     */
1689    public JSONObject element( String key, Object value, JsonConfig jsonConfig ) {
1690       verifyIsNull();
1691       if( key == null ){
1692          throw new JSONException( "Null key." );
1693       }
1694       if( value != null ){
1695          value = processValue( key, value, jsonConfig );
1696          _setInternal( key, value, jsonConfig );
1697       }else{
1698          remove( key );
1699       }
1700       return this;
1701    }
1702 
1703    /**
1704     * Put a key/value pair in the JSONObject, but only if the key and the value
1705     * are both non-null.
1706     *
1707     * @param key A key string.
1708     * @param value An object which is the value. It should be of one of these
1709     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
1710     *        String, or the JSONNull object.
1711     * @return this.
1712     * @throws JSONException If the value is a non-finite number.
1713     */
1714    public JSONObject elementOpt( String key, Object value ) {
1715       return elementOpt( key, value, new JsonConfig() );
1716    }
1717 
1718    /**
1719     * Put a key/value pair in the JSONObject, but only if the key and the value
1720     * are both non-null.
1721     *
1722     * @param key A key string.
1723     * @param value An object which is the value. It should be of one of these
1724     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
1725     *        String, or the JSONNull object.
1726     * @return this.
1727     * @throws JSONException If the value is a non-finite number.
1728     */
1729    public JSONObject elementOpt( String key, Object value, JsonConfig jsonConfig ) {
1730       verifyIsNull();
1731       if( key != null && value != null ){
1732          element( key, value, jsonConfig );
1733       }
1734       return this;
1735    }
1736 
1737    public Set entrySet() {
1738       return Collections.unmodifiableSet( properties.entrySet() );
1739    }
1740 
1741    public boolean equals( Object obj ) {
1742       if( obj == this ){
1743          return true;
1744       }
1745       if( obj == null ){
1746          return false;
1747       }
1748 
1749       if( !(obj instanceof JSONObject) ){
1750          return false;
1751       }
1752 
1753       JSONObject other = (JSONObject) obj;
1754 
1755       if( isNullObject() ){
1756          if( other.isNullObject() ){
1757             return true;
1758          }else{
1759             return false;
1760          }
1761       }else{
1762          if( other.isNullObject() ){
1763             return false;
1764          }
1765       }
1766 
1767       if( other.size() != size() ){
1768          return false;
1769       }
1770 
1771       for( Iterator keys = properties.keySet()
1772             .iterator(); keys.hasNext(); ){
1773          String key = (String) keys.next();
1774          if( !other.properties.containsKey( key ) ){
1775             return false;
1776          }
1777          Object o1 = properties.get( key );
1778          Object o2 = other.properties.get( key );
1779 
1780          if( JSONNull.getInstance()
1781                .equals( o1 ) ){
1782             if( JSONNull.getInstance()
1783                   .equals( o2 ) ){
1784                continue;
1785             }else{
1786                return false;
1787             }
1788          }else{
1789             if( JSONNull.getInstance()
1790                   .equals( o2 ) ){
1791                return false;
1792             }
1793          }
1794 
1795          if( o1 instanceof String && o2 instanceof JSONFunction ){
1796             if( !o1.equals( String.valueOf( o2 ) ) ){
1797                return false;
1798             }
1799          }else if( o1 instanceof JSONFunction && o2 instanceof String ){
1800             if( !o2.equals( String.valueOf( o1 ) ) ){
1801                return false;
1802             }
1803          }else if( o1 instanceof JSONObject && o2 instanceof JSONObject ){
1804             if( !o1.equals( o2 ) ){
1805                return false;
1806             }
1807          }else if( o1 instanceof JSONArray && o2 instanceof JSONArray ){
1808             if( !o1.equals( o2 ) ){
1809                return false;
1810             }
1811          }else if( o1 instanceof JSONFunction && o2 instanceof JSONFunction ){
1812             if( !o1.equals( o2 ) ){
1813                return false;
1814             }
1815          }else{
1816             if( o1 instanceof String ){
1817                if( !o1.equals( String.valueOf( o2 ) ) ){
1818                   return false;
1819                }
1820             }else if( o2 instanceof String ){
1821                if( !o2.equals( String.valueOf( o1 ) ) ){
1822                   return false;
1823                }
1824             }else{
1825                Morpher m1 = JSONUtils.getMorpherRegistry()
1826                      .getMorpherFor( o1.getClass() );
1827                Morpher m2 = JSONUtils.getMorpherRegistry()
1828                      .getMorpherFor( o2.getClass() );
1829                if( m1 != null && m1 != IdentityObjectMorpher.getInstance() ){
1830                   if( !o1.equals( JSONUtils.getMorpherRegistry()
1831                         .morph( o1.getClass(), o2 ) ) ){
1832                      return false;
1833                   }
1834                }else if( m2 != null && m2 != IdentityObjectMorpher.getInstance() ){
1835                   if( !JSONUtils.getMorpherRegistry()
1836                         .morph( o1.getClass(), o1 )
1837                         .equals( o2 ) ){
1838                      return false;
1839                   }
1840                }else{
1841                   if( !o1.equals( o2 ) ){
1842                      return false;
1843                   }
1844                }
1845             }
1846          }
1847       }
1848       return true;
1849    }
1850 
1851    public Object get( Object key ) {
1852       if( key instanceof String ){
1853          return get( (String) key );
1854       }
1855       return null;
1856    }
1857 
1858    /**
1859     * Get the value object associated with a key.
1860     *
1861     * @param key A key string.
1862     * @return The object associated with the key.
1863     * @throws JSONException if this.isNull() returns true.
1864     */
1865    public Object get( String key ) {
1866       verifyIsNull();
1867       return this.properties.get( key );
1868    }
1869 
1870    /**
1871     * Get the boolean value associated with a key.
1872     *
1873     * @param key A key string.
1874     * @return The truth.
1875     * @throws JSONException if the value is not a Boolean or the String "true"
1876     *         or "false".
1877     */
1878    public boolean getBoolean( String key ) {
1879       verifyIsNull();
1880       Object o = get( key );
1881       if( o != null ){
1882          if( o.equals( Boolean.FALSE )
1883                || (o instanceof String && ((String) o).equalsIgnoreCase( "false" )) ){
1884             return false;
1885          }else if( o.equals( Boolean.TRUE )
1886                || (o instanceof String && ((String) o).equalsIgnoreCase( "true" )) ){
1887             return true;
1888          }
1889       }
1890       throw new JSONException( "JSONObject[" + JSONUtils.quote( key ) + "] is not a Boolean." );
1891    }
1892 
1893    /**
1894     * Get the double value associated with a key.
1895     *
1896     * @param key A key string.
1897     * @return The numeric value.
1898     * @throws JSONException if the key is not found or if the value is not a
1899     *         Number object and cannot be converted to a number.
1900     */
1901    public double getDouble( String key ) {
1902       verifyIsNull();
1903       Object o = get( key );
1904       if( o != null ){
1905          try{
1906             return o instanceof Number ? ((Number) o).doubleValue()
1907                   : Double.parseDouble( (String) o );
1908          }catch( Exception e ){
1909             throw new JSONException( "JSONObject[" + JSONUtils.quote( key ) + "] is not a number." );
1910          }
1911       }
1912       throw new JSONException( "JSONObject[" + JSONUtils.quote( key ) + "] is not a number." );
1913    }
1914 
1915    /**
1916     * Get the int value associated with a key. If the number value is too large
1917     * for an int, it will be clipped.
1918     *
1919     * @param key A key string.
1920     * @return The integer value.
1921     * @throws JSONException if the key is not found or if the value cannot be
1922     *         converted to an integer.
1923     */
1924    public int getInt( String key ) {
1925       verifyIsNull();
1926       Object o = get( key );
1927       if( o != null ){
1928          return o instanceof Number ? ((Number) o).intValue() : (int) getDouble( key );
1929       }
1930       throw new JSONException( "JSONObject[" + JSONUtils.quote( key ) + "] is not a number." );
1931    }
1932 
1933    /**
1934     * Get the JSONArray value associated with a key.
1935     *
1936     * @param key A key string.
1937     * @return A JSONArray which is the value.
1938     * @throws JSONException if the key is not found or if the value is not a
1939     *         JSONArray.
1940     */
1941    public JSONArray getJSONArray( String key ) {
1942       verifyIsNull();
1943       Object o = get( key );
1944       if( o != null && o instanceof JSONArray ){
1945          return (JSONArray) o;
1946       }
1947       throw new JSONException( "JSONObject[" + JSONUtils.quote( key ) + "] is not a JSONArray." );
1948    }
1949 
1950    /**
1951     * Get the JSONObject value associated with a key.
1952     *
1953     * @param key A key string.
1954     * @return A JSONObject which is the value.
1955     * @throws JSONException if the key is not found or if the value is not a
1956     *         JSONObject.
1957     */
1958    public JSONObject getJSONObject( String key ) {
1959       verifyIsNull();
1960       Object o = get( key );
1961       if( JSONNull.getInstance()
1962             .equals( o ) ){
1963          return new JSONObject( true );
1964       }else if( o instanceof JSONObject ){
1965          return (JSONObject) o;
1966       }
1967       throw new JSONException( "JSONObject[" + JSONUtils.quote( key ) + "] is not a JSONObject." );
1968    }
1969 
1970    /**
1971     * Get the long value associated with a key. If the number value is too long
1972     * for a long, it will be clipped.
1973     *
1974     * @param key A key string.
1975     * @return The long value.
1976     * @throws JSONException if the key is not found or if the value cannot be
1977     *         converted to a long.
1978     */
1979    public long getLong( String key ) {
1980       verifyIsNull();
1981       Object o = get( key );
1982       if( o != null ){
1983          return o instanceof Number ? ((Number) o).longValue() : (long) getDouble( key );
1984       }
1985       throw new JSONException( "JSONObject[" + JSONUtils.quote( key ) + "] is not a number." );
1986    }
1987 
1988    /**
1989     * Get the string associated with a key.
1990     *
1991     * @param key A key string.
1992     * @return A string which is the value.
1993     * @throws JSONException if the key is not found.
1994     */
1995    public String getString( String key ) {
1996       verifyIsNull();
1997       Object o = get( key );
1998       if( o != null ){
1999          return o.toString();
2000       }
2001       throw new JSONException( "JSONObject[" + JSONUtils.quote( key ) + "] not found." );
2002    }
2003 
2004    /**
2005     * Determine if the JSONObject contains a specific key.
2006     *
2007     * @param key A key string.
2008     * @return true if the key exists in the JSONObject.
2009     */
2010    public boolean has( String key ) {
2011       verifyIsNull();
2012       return this.properties.containsKey( key );
2013    }
2014 
2015    public int hashCode() {
2016       int hashcode = 19;
2017       if( isNullObject() ){
2018          return hashcode + JSONNull.getInstance()
2019                .hashCode();
2020       }
2021       for( Iterator entries = properties.entrySet()
2022             .iterator(); entries.hasNext(); ){
2023          Map.Entry entry = (Map.Entry) entries.next();
2024          Object key = entry.getKey();
2025          Object value = entry.getValue();
2026          hashcode += key.hashCode() + JSONUtils.hashCode( value );
2027       }
2028       return hashcode;
2029    }
2030 
2031    public boolean isArray() {
2032       return false;
2033    }
2034 
2035    public boolean isEmpty() {
2036       // verifyIsNull();
2037       return this.properties.isEmpty();
2038    }
2039 
2040    /**
2041     * Returs if this object is a null JSONObject.
2042     */
2043    public boolean isNullObject() {
2044       return nullObject;
2045    }
2046 
2047    /**
2048     * Get an enumeration of the keys of the JSONObject.
2049     *
2050     * @return An iterator of the keys.
2051     */
2052    public Iterator keys() {
2053       verifyIsNull();
2054       return keySet().iterator();
2055    }
2056 
2057    public Set keySet() {
2058       return Collections.unmodifiableSet( properties.keySet() );
2059    }
2060 
2061    /**
2062     * Produce a JSONArray containing the names of the elements of this
2063     * JSONObject.
2064     *
2065     * @return A JSONArray containing the key strings, or null if the JSONObject
2066     *         is empty.
2067     */
2068    public JSONArray names() {
2069       return names( new JsonConfig() );
2070    }
2071    
2072    /**
2073     * Produce a JSONArray containing the names of the elements of this
2074     * JSONObject.
2075     *
2076     * @return A JSONArray containing the key strings, or null if the JSONObject
2077     *         is empty.
2078     */
2079    public JSONArray names( JsonConfig jsonConfig ) {
2080       verifyIsNull();
2081       JSONArray ja = new JSONArray();
2082       Iterator keys = keys();
2083       while( keys.hasNext() ){
2084          ja.element( keys.next(), jsonConfig );
2085       }
2086       return ja;
2087    }
2088 
2089    /**
2090     * Get an optional value associated with a key.
2091     *
2092     * @param key A key string.
2093     * @return An object which is the value, or null if there is no value.
2094     */
2095    public Object opt( String key ) {
2096       verifyIsNull();
2097       return key == null ? null : this.properties.get( key );
2098    }
2099 
2100    /**
2101     * Get an optional boolean associated with a key. It returns false if there
2102     * is no such key, or if the value is not Boolean.TRUE or the String "true".
2103     *
2104     * @param key A key string.
2105     * @return The truth.
2106     */
2107    public boolean optBoolean( String key ) {
2108       verifyIsNull();
2109       return optBoolean( key, false );
2110    }
2111 
2112    /**
2113     * Get an optional boolean associated with a key. It returns the defaultValue
2114     * if there is no such key, or if it is not a Boolean or the String "true" or
2115     * "false" (case insensitive).
2116     *
2117     * @param key A key string.
2118     * @param defaultValue The default.
2119     * @return The truth.
2120     */
2121    public boolean optBoolean( String key, boolean defaultValue ) {
2122       verifyIsNull();
2123       try{
2124          return getBoolean( key );
2125       }catch( Exception e ){
2126          return defaultValue;
2127       }
2128    }
2129 
2130    /**
2131     * Get an optional double associated with a key, or NaN if there is no such
2132     * key or if its value is not a number. If the value is a string, an attempt
2133     * will be made to evaluate it as a number.
2134     *
2135     * @param key A string which is the key.
2136     * @return An object which is the value.
2137     */
2138    public double optDouble( String key ) {
2139       verifyIsNull();
2140       return optDouble( key, Double.NaN );
2141    }
2142 
2143    /**
2144     * Get an optional double associated with a key, or the defaultValue if there
2145     * is no such key or if its value is not a number. If the value is a string,
2146     * an attempt will be made to evaluate it as a number.
2147     *
2148     * @param key A key string.
2149     * @param defaultValue The default.
2150     * @return An object which is the value.
2151     */
2152    public double optDouble( String key, double defaultValue ) {
2153       verifyIsNull();
2154       try{
2155          Object o = opt( key );
2156          return o instanceof Number ? ((Number) o).doubleValue()
2157                : new Double( (String) o ).doubleValue();
2158       }catch( Exception e ){
2159          return defaultValue;
2160       }
2161    }
2162 
2163    /**
2164     * Get an optional int value associated with a key, or zero if there is no
2165     * such key or if the value is not a number. If the value is a string, an
2166     * attempt will be made to evaluate it as a number.
2167     *
2168     * @param key A key string.
2169     * @return An object which is the value.
2170     */
2171    public int optInt( String key ) {
2172       verifyIsNull();
2173       return optInt( key, 0 );
2174    }
2175 
2176    /**
2177     * Get an optional int value associated with a key, or the default if there
2178     * is no such key or if the value is not a number. If the value is a string,
2179     * an attempt will be made to evaluate it as a number.
2180     *
2181     * @param key A key string.
2182     * @param defaultValue The default.
2183     * @return An object which is the value.
2184     */
2185    public int optInt( String key, int defaultValue ) {
2186       verifyIsNull();
2187       try{
2188          return getInt( key );
2189       }catch( Exception e ){
2190          return defaultValue;
2191       }
2192    }
2193 
2194    /**
2195     * Get an optional JSONArray associated with a key. It returns null if there
2196     * is no such key, or if its value is not a JSONArray.
2197     *
2198     * @param key A key string.
2199     * @return A JSONArray which is the value.
2200     */
2201    public JSONArray optJSONArray( String key ) {
2202       verifyIsNull();
2203       Object o = opt( key );
2204       return o instanceof JSONArray ? (JSONArray) o : null;
2205    }
2206 
2207    /**
2208     * Get an optional JSONObject associated with a key. It returns null if there
2209     * is no such key, or if its value is not a JSONObject.
2210     *
2211     * @param key A key string.
2212     * @return A JSONObject which is the value.
2213     */
2214    public JSONObject optJSONObject( String key ) {
2215       verifyIsNull();
2216       Object o = opt( key );
2217       return o instanceof JSONObject ? (JSONObject) o : null;
2218    }
2219 
2220    /**
2221     * Get an optional long value associated with a key, or zero if there is no
2222     * such key or if the value is not a number. If the value is a string, an
2223     * attempt will be made to evaluate it as a number.
2224     *
2225     * @param key A key string.
2226     * @return An object which is the value.
2227     */
2228    public long optLong( String key ) {
2229       verifyIsNull();
2230       return optLong( key, 0 );
2231    }
2232 
2233    /**
2234     * Get an optional long value associated with a key, or the default if there
2235     * is no such key or if the value is not a number. If the value is a string,
2236     * an attempt will be made to evaluate it as a number.
2237     *
2238     * @param key A key string.
2239     * @param defaultValue The default.
2240     * @return An object which is the value.
2241     */
2242    public long optLong( String key, long defaultValue ) {
2243       verifyIsNull();
2244       try{
2245          return getLong( key );
2246       }catch( Exception e ){
2247          return defaultValue;
2248       }
2249    }
2250 
2251    /**
2252     * Get an optional string associated with a key. It returns an empty string
2253     * if there is no such key. If the value is not a string and is not null,
2254     * then it is coverted to a string.
2255     *
2256     * @param key A key string.
2257     * @return A string which is the value.
2258     */
2259    public String optString( String key ) {
2260       verifyIsNull();
2261       return optString( key, "" );
2262    }
2263 
2264    /**
2265     * Get an optional string associated with a key. It returns the defaultValue
2266     * if there is no such key.
2267     *
2268     * @param key A key string.
2269     * @param defaultValue The default.
2270     * @return A string which is the value.
2271     */
2272    public String optString( String key, String defaultValue ) {
2273       verifyIsNull();
2274       Object o = opt( key );
2275       return o != null ? o.toString() : defaultValue;
2276    }
2277 
2278    public Object put( Object key, Object value ) {
2279       if( key == null ){
2280          throw new IllegalArgumentException( "key is null." );
2281       }
2282       Object previous = properties.get( key );
2283       element( String.valueOf( key ), value );
2284       return previous;
2285    }
2286 
2287    public void putAll( Map map ) {
2288       putAll( map, new JsonConfig() );
2289    }
2290 
2291    public void putAll( Map map, JsonConfig jsonConfig ) {
2292       if( map instanceof JSONObject ){
2293          for( Iterator entries = map.entrySet()
2294                .iterator(); entries.hasNext(); ){
2295             Map.Entry entry = (Map.Entry) entries.next();
2296             String key = (String) entry.getKey();
2297             Object value = entry.getValue();
2298             this.properties.put( key, value );
2299          }
2300       }else{
2301          for( Iterator entries = map.entrySet()
2302                .iterator(); entries.hasNext(); ){
2303             Map.Entry entry = (Map.Entry) entries.next();
2304             String key = String.valueOf( entry.getKey() );
2305             Object value = entry.getValue();
2306             element( key, value, jsonConfig );
2307          }
2308       }
2309    }
2310 
2311    public Object remove( Object key ) {
2312       return properties.remove( key );
2313    }
2314 
2315    /**
2316     * Remove a name and its value, if present.
2317     *
2318     * @param key The name to be removed.
2319     * @return The value that was associated with the name, or null if there was
2320     *         no value.
2321     */
2322    public Object remove( String key ) {
2323       verifyIsNull();
2324       return this.properties.remove( key );
2325    }
2326 
2327    /**
2328     * Get the number of keys stored in the JSONObject.
2329     *
2330     * @return The number of keys in the JSONObject.
2331     */
2332    public int size() {
2333       // verifyIsNull();
2334       return this.properties.size();
2335    }
2336 
2337    /**
2338     * Produce a JSONArray containing the values of the members of this
2339     * JSONObject.
2340     *
2341     * @param names A JSONArray containing a list of key strings. This determines
2342     *        the sequence of the values in the result.
2343     * @return A JSONArray of values.
2344     * @throws JSONException If any of the values are non-finite numbers.
2345     */
2346    public JSONArray toJSONArray( JSONArray names ) {
2347       verifyIsNull();
2348       if( names == null || names.size() == 0 ){
2349          return null;
2350       }
2351       JSONArray ja = new JSONArray();
2352       for( int i = 0; i < names.size(); i += 1 ){
2353          ja.element( this.opt( names.getString( i ) ) );
2354       }
2355       return ja;
2356    }
2357 
2358    /**
2359     * Make a JSON text of this JSONObject. For compactness, no whitespace is
2360     * added. If this would not result in a syntactically correct JSON text, then
2361     * null will be returned instead.
2362     * <p>
2363     * Warning: This method assumes that the data structure is acyclical.
2364     *
2365     * @return a printable, displayable, portable, transmittable representation
2366     *         of the object, beginning with <code>{</code>&nbsp;<small>(left
2367     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
2368     *         brace)</small>.
2369     */
2370    public String toString() {
2371       if( isNullObject() ){
2372          return JSONNull.getInstance()
2373                .toString();
2374       }
2375       try{
2376          Iterator keys = keys();
2377          StringBuffer sb = new StringBuffer( "{" );
2378 
2379          while( keys.hasNext() ){
2380             if( sb.length() > 1 ){
2381                sb.append( ',' );
2382             }
2383             Object o = keys.next();
2384             sb.append( JSONUtils.quote( o.toString() ) );
2385             sb.append( ':' );
2386             sb.append( JSONUtils.valueToString( this.properties.get( o ) ) );
2387          }
2388          sb.append( '}' );
2389          return sb.toString();
2390       }catch( Exception e ){
2391          return null;
2392       }
2393    }
2394 
2395    /**
2396     * Make a prettyprinted JSON text of this JSONObject.
2397     * <p>
2398     * Warning: This method assumes that the data structure is acyclical.
2399     *
2400     * @param indentFactor The number of spaces to add to each level of
2401     *        indentation.
2402     * @return a printable, displayable, portable, transmittable representation
2403     *         of the object, beginning with <code>{</code>&nbsp;<small>(left
2404     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
2405     *         brace)</small>.
2406     * @throws JSONException If the object contains an invalid number.
2407     */
2408    public String toString( int indentFactor ) {
2409       if( isNullObject() ){
2410          return JSONNull.getInstance()
2411                .toString();
2412       }
2413       if( indentFactor == 0 ){
2414          return this.toString();
2415       }
2416       return toString( indentFactor, 0 );
2417    }
2418 
2419    /**
2420     * Make a prettyprinted JSON text of this JSONObject.
2421     * <p>
2422     * Warning: This method assumes that the data structure is acyclical.
2423     *
2424     * @param indentFactor The number of spaces to add to each level of
2425     *        indentation.
2426     * @param indent The indentation of the top level.
2427     * @return a printable, displayable, transmittable representation of the
2428     *         object, beginning with <code>{</code>&nbsp;<small>(left brace)</small>
2429     *         and ending with <code>}</code>&nbsp;<small>(right brace)</small>.
2430     * @throws JSONException If the object contains an invalid number.
2431     */
2432    public String toString( int indentFactor, int indent ) {
2433       if( isNullObject() ){
2434          return JSONNull.getInstance()
2435                .toString();
2436       }
2437       int i;
2438       int n = size();
2439       if( n == 0 ){
2440          return "{}";
2441       }
2442       if( indentFactor == 0 ){
2443          return this.toString();
2444       }
2445       Iterator keys = keys();
2446       StringBuffer sb = new StringBuffer( "{" );
2447       int newindent = indent + indentFactor;
2448       Object o;
2449       if( n == 1 ){
2450          o = keys.next();
2451          sb.append( JSONUtils.quote( o.toString() ) );
2452          sb.append( ": " );
2453          sb.append( JSONUtils.valueToString( this.properties.get( o ), indentFactor, indent ) );
2454       }else{
2455          while( keys.hasNext() ){
2456             o = keys.next();
2457             if( sb.length() > 1 ){
2458                sb.append( ",\n" );
2459             }else{
2460                sb.append( '\n' );
2461             }
2462             for( i = 0; i < newindent; i += 1 ){
2463                sb.append( ' ' );
2464             }
2465             sb.append( JSONUtils.quote( o.toString() ) );
2466             sb.append( ": " );
2467             sb.append( JSONUtils.valueToString( this.properties.get( o ), indentFactor, newindent ) );
2468          }
2469          if( sb.length() > 1 ){
2470             sb.append( '\n' );
2471             for( i = 0; i < indent; i += 1 ){
2472                sb.append( ' ' );
2473             }
2474          }
2475          for( i = 0; i < indent; i += 1 ){
2476             sb.insert( 0, ' ' );
2477          }
2478       }
2479       sb.append( '}' );
2480       return sb.toString();
2481    }
2482 
2483    public Collection values() {
2484       return Collections.unmodifiableCollection( properties.values() );
2485    }
2486 
2487    /**
2488     * Write the contents of the JSONObject as JSON text to a writer. For
2489     * compactness, no whitespace is added.
2490     * <p>
2491     * Warning: This method assumes that the data structure is acyclical.
2492     *
2493     * @return The writer.
2494     * @throws JSONException
2495     */
2496    public Writer write( Writer writer ) {
2497       try{
2498          if( isNullObject() ){
2499             writer.write( JSONNull.getInstance()
2500                   .toString() );
2501             return writer;
2502          }
2503 
2504          boolean b = false;
2505          Iterator keys = keys();
2506          writer.write( '{' );
2507 
2508          while( keys.hasNext() ){
2509             if( b ){
2510                writer.write( ',' );
2511             }
2512             Object k = keys.next();
2513             writer.write( JSONUtils.quote( k.toString() ) );
2514             writer.write( ':' );
2515             Object v = this.properties.get( k );
2516             if( v instanceof JSONObject ){
2517                ((JSONObject) v).write( writer );
2518             }else if( v instanceof JSONArray ){
2519                ((JSONArray) v).write( writer );
2520             }else{
2521                writer.write( JSONUtils.valueToString( v ) );
2522             }
2523             b = true;
2524          }
2525          writer.write( '}' );
2526          return writer;
2527       }catch( IOException e ){
2528          throw new JSONException( e );
2529       }
2530    }
2531 
2532    private JSONObject _accumulate( String key, Object value, JsonConfig jsonConfig ) {
2533       if( isNullObject() ){
2534          throw new JSONException( "Can't accumulate on null object" );
2535       }
2536 
2537       if( !has( key ) ){
2538          setInternal( key, value, jsonConfig );
2539       }else{
2540          Object o = opt( key );
2541          if( o instanceof JSONArray ){
2542             ((JSONArray) o).element( value, jsonConfig );
2543          }else{
2544             setInternal( key, new JSONArray().element( o )
2545                   .element( value, jsonConfig ), jsonConfig );
2546          }
2547       }
2548 
2549       return this;
2550    }
2551 
2552    protected Object _processValue( Object value, JsonConfig jsonConfig ) {
2553       if( value instanceof JSONTokener ) {
2554          return _fromJSONTokener( (JSONTokener) value, jsonConfig );
2555       }
2556       return super._processValue( value, jsonConfig );
2557    }
2558 
2559    /**
2560     * Put a key/value pair in the JSONObject.
2561     *
2562     * @param key A key string.
2563     * @param value An object which is the value. It should be of one of these
2564     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
2565     *        String, or the JSONNull object.
2566     * @return this.
2567     * @throws JSONException If the value is non-finite number or if the key is
2568     *         null.
2569     */
2570    private JSONObject _setInternal( String key, Object value, JsonConfig jsonConfig ) {
2571       verifyIsNull();
2572       if( key == null ){
2573          throw new JSONException( "Null key." );
2574       }
2575 
2576       if( JSONUtils.isString( value ) && JSONUtils.mayBeJSON( String.valueOf( value ) ) ){
2577          this.properties.put( key, value );
2578       }else{
2579          /*
2580          Object jo = _processValue( value, jsonConfig );
2581          if( CycleDetectionStrategy.IGNORE_PROPERTY_OBJ == jo
2582                || CycleDetectionStrategy.IGNORE_PROPERTY_ARR == jo ){
2583             // do nothing
2584          }else{
2585             this.properties.put( key, jo );
2586          }
2587          */
2588          if( CycleDetectionStrategy.IGNORE_PROPERTY_OBJ == value
2589                || CycleDetectionStrategy.IGNORE_PROPERTY_ARR == value ){
2590             // do nothing
2591          }else{
2592             this.properties.put( key, value );
2593          }
2594       }
2595 
2596       return this;
2597    }
2598 
2599    private Object processValue( Object value, JsonConfig jsonConfig ) {
2600       if( value != null ){
2601          JsonValueProcessor processor = jsonConfig.findJsonValueProcessor( value.getClass() );
2602          if( processor != null ){
2603             value = processor.processObjectValue( null, value, jsonConfig );
2604             if( !JsonVerifier.isValidJsonValue( value ) ){
2605                throw new JSONException( "Value is not a valid JSON value. " + value );
2606             }
2607          }
2608       }
2609       return _processValue( value, jsonConfig );
2610    }
2611 
2612    private Object processValue( String key, Object value, JsonConfig jsonConfig ) {
2613       if( value != null ){
2614          JsonValueProcessor processor = jsonConfig.findJsonValueProcessor( value.getClass(), key );
2615          if( processor != null ){
2616             value = processor.processObjectValue( null, value, jsonConfig );
2617             if( !JsonVerifier.isValidJsonValue( value ) ){
2618                throw new JSONException( "Value is not a valid JSON value. " + value );
2619             }
2620          }
2621       }
2622       return _processValue( value, jsonConfig );
2623    }
2624 
2625    /**
2626     * Put a key/value pair in the JSONObject.
2627     *
2628     * @param key A key string.
2629     * @param value An object which is the value. It should be of one of these
2630     *        types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
2631     *        String, or the JSONNull object.
2632     * @return this.
2633     * @throws JSONException If the value is non-finite number or if the key is
2634     *         null.
2635     */
2636    private JSONObject setInternal( String key, Object value, JsonConfig jsonConfig ) {
2637       return _setInternal( key, processValue( key, value, jsonConfig ), jsonConfig );
2638    }
2639 
2640    /**
2641     * Checks if this object is a "null" object.
2642     */
2643    private void verifyIsNull() {
2644       if( isNullObject() ){
2645          throw new JSONException( "null object" );
2646       }
2647    }
2648 }