Open Source Repository

Home /json/json-lib-2.4-jdk15 | Repository Home


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

package net.sf.json.util;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.sf.ezmorph.MorphUtils;
import net.sf.ezmorph.MorpherRegistry;
import net.sf.ezmorph.bean.MorphDynaBean;
import net.sf.ezmorph.bean.MorphDynaClass;
import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONFunction;
import net.sf.json.JSONNull;
import net.sf.json.JSONObject;
import net.sf.json.JSONString;
import net.sf.json.JsonConfig;
import net.sf.json.regexp.RegexpUtils;

import org.apache.commons.beanutils.DynaBean;

/**
 * Provides useful methods on java objects and JSON values.
 *
 @author Andres Almiray <[email protected]>
 @version 7
 */
public final class JSONUtils {
   /** Constant for char " */
   public static final String DOUBLE_QUOTE = "\"";
   /** Constant for char ' */
   public static final String SINGLE_QUOTE = "'";

   private static final String FUNCTION_BODY_PATTERN = "^function[ ]?\\(.*?\\)[ \n\t]*\\{(.*?)\\}$";
   private static final String FUNCTION_HEADER_PATTERN = "^function[ ]?\\(.*?\\)$";
   private static final String FUNCTION_PARAMS_PATTERN = "^function[ ]?\\((.*?)\\).*";
   private static final String FUNCTION_PATTERN = "^function[ ]?\\(.*?\\)[ \n\t]*\\{.*?\\}$";
   private static final String FUNCTION_PREFIX = "function";

   private static final MorpherRegistry morpherRegistry = new MorpherRegistry();

   static{
      // register standard morphers
      MorphUtils.registerStandardMorphersmorpherRegistry );
   }

   /**
    * Transforms the string into a valid Java Identifier.<br>
    * The default strategy is JavaIdentifierTransformer.NOOP
    *
    @throws JSONException if the string can not be transformed.
    */
   public static String convertToJavaIdentifierString key ) {
      return convertToJavaIdentifierkey, new JsonConfig() );
   }

   /**
    * Transforms the string into a valid Java Identifier.<br>
    * The default strategy is JavaIdentifierTransformer.NOOP
    *
    @throws JSONException if the string can not be transformed.
    */
   public static String convertToJavaIdentifierString key, JsonConfig jsonConfig ) {
      try{
         return jsonConfig.getJavaIdentifierTransformer()
               .transformToJavaIdentifierkey );
      }catchJSONException jsone ){
         throw jsone;
      }catchException e ){
         throw new JSONException);
      }
   }

   /**
    * Produce a string from a double. The string "null" will be returned if the
    * number is not finite.
    *
    @param d A double.
    @return A String.
    */
   public static String doubleToStringdouble ) {
      ifDouble.isInfinite|| Double.isNaN) ){
         return "null";
      }

      // Shave off trailing zeros and decimal point, if possible.

      String s = Double.toString);
      ifs.indexOf'.' && s.indexOf'e' && s.indexOf'E' ){
         whiles.endsWith"0" ) ){
            s = s.substring0, s.length() );
         }
         ifs.endsWith"." ) ){
            s = s.substring0, s.length() );
         }
      }
      return s;
   }

   /**
    * Returns the body of a function literal.
    */
   public static String getFunctionBodyString function ) {
      return RegexpUtils.getMatcherFUNCTION_BODY_PATTERN, true ).getGroupIfMatchesfunction, );
   }
   
   /**
    * Returns the params of a function literal.
    */
   public static String getFunctionParamsString function ) {
      return RegexpUtils.getMatcherFUNCTION_PARAMS_PATTERN, true ).getGroupIfMatchesfunction, );
   }

   /**
    * Returns the inner-most component type of an Array.
    */
   public static Class getInnerComponentTypeClass type ) {
      if!type.isArray() ){
         return type;
      }
      return getInnerComponentTypetype.getComponentType() );
   }

   /**
    * Returns the singleton MorpherRegistry.
    */
   public static MorpherRegistry getMorpherRegistry() {
      return morpherRegistry;
   }

   /**
    * Creates a Map with all the properties of the JSONObject.
    */
   public static Map getPropertiesJSONObject jsonObject ) {
      Map properties = new HashMap();
      forIterator keys = jsonObject.keys(); keys.hasNext()){
         String key = (Stringkeys.next();
         /*
          * String parsedKey = key; if( !JSONUtils.isJavaIdentifier( parsedKey ) ){
          * parsedKey = JSONUtils.convertToJavaIdentifier( key ); }
          */
         properties.putkey, getTypeClassjsonObject.getkey ) ) );
      }
      return properties;
   }

   /**
    * Returns the JSON type.<br>
    * Values are Object, String, Boolean, Number(subclasses) &amp; JSONFunction.
    */
   public static Class getTypeClassObject obj ) {
      ifisNullobj ) ){
         return Object.class;
      }else ifisArrayobj ) ){
         return List.class;
      }else ifisFunctionobj ) ){
         return JSONFunction.class;
      }else ifisBooleanobj ) ){
         return Boolean.class;
      }else ifisNumberobj ) ){
         Number n = (Numberobj;
         ifisInteger) ){
            return Integer.class;
         }else ifisLong) ){
            return Long.class;
         }else ifisFloat) ){
            return Float.class;
         }else ifisBigInteger) ){
            return BigInteger.class;
         }else ifisBigDecimal) ){
            return BigDecimal.class;
         }else ifisDouble) ){
            return Double.class;
         }else{
            throw new JSONException"Unsupported type" );
         }
      }else ifisStringobj ) ){
         return String.class;
      }else ifisObjectobj ) ){
         return Object.class;
      }else{
         throw new JSONException"Unsupported type" );
      }
   }

   /**
    * Returns the hashcode of value.<br>
    * If null it will return JSONNull.getInstance().hashCode().<br>
    * If value is JSON, JSONFunction or String, value.hashCode is returned,
    * otherwise the value is transformed to a String an its hashcode is
    * returned.
    */
   public static int hashCodeObject value ) {
      ifvalue == null ){
         return JSONNull.getInstance()
               .hashCode();
      }else ifvalue instanceof JSON || value instanceof String || value instanceof JSONFunction ){
         return value.hashCode();
      }else{
         return String.valueOfvalue )
               .hashCode();
      }
   }

   /**
    * Tests if a Class represents an array or Collection.
    */
   public static boolean isArrayClass clazz ) {
      return clazz != null
            && (clazz.isArray() || Collection.class.isAssignableFromclazz || (JSONArray.class.isAssignableFromclazz )));
   }

   /**
    * Tests if obj is an array or Collection.
    */
   public static boolean isArrayObject obj ) {
      if( (obj != null && obj.getClass()
            .isArray()) || (obj instanceof Collection|| (obj instanceof JSONArray) ){
         return true;
      }
      return false;
   }

   /**
    * Tests if Class represents a Boolean or primitive boolean
    */
   public static boolean isBooleanClass clazz ) {
      return clazz != null
            && (Boolean.TYPE.isAssignableFromclazz || Boolean.class.isAssignableFromclazz ));
   }

   /**
    * Tests if obj is a Boolean or primitive boolean
    */
   public static boolean isBooleanObject obj ) {
      if( (obj instanceof Boolean|| (obj != null && obj.getClass() == Boolean.TYPE) ){
         return true;
      }
      return false;
   }

   /**
    * Tests if Class represents a primitive double or wrapper.<br>
    */
   public static boolean isDoubleClass clazz ) {
      return clazz != null
            && (Double.TYPE.isAssignableFromclazz || Double.class.isAssignableFromclazz ));
   }

   /**
    * Tests if obj is javaScript function.<br>
    * Obj must be a non-null String and match <nowrap>"^function[ ]?\\(.*\\)[
    * ]?\\{.*\\}$"</nowrap>
    */
   public static boolean isFunctionObject obj ) {
      ifobj instanceof String ){
         String str = (Stringobj;
         return str.startsWithFUNCTION_PREFIX && RegexpUtils.getMatcherFUNCTION_PATTERN, true ).matchesstr );
      }
      ifobj instanceof JSONFunction ){
         return true;
      }
      return false;
   }

   /**
    * Tests if obj is javaScript function header.<br>
    * Obj must be a non-null String and match "^function[ ]?\\(.*\\)$"
    */
   public static boolean isFunctionHeaderObject obj ) {
      ifobj instanceof String ){
         String str = (Stringobj;
         return str.startsWithFUNCTION_PREFIX && RegexpUtils.getMatcherFUNCTION_HEADER_PATTERN, true ).matchesstr );
      }
      return false;
   }

   /**
    * Returns trus if str represents a valid Java identifier.
    */
   public static boolean isJavaIdentifierString str ) {
      ifstr.length() == || !Character.isJavaIdentifierStartstr.charAt) ) ){
         return false;
      }
      forint i = 1; i < str.length(); i++ ){
         if!Character.isJavaIdentifierPartstr.charAt) ) ){
            return false;
         }
      }
      return true;
   }

   /**
    * Tests if the obj is a javaScript null.
    */
   public static boolean isNullObject obj ) {
      ifobj instanceof JSONObject ){
         return ((JSONObjectobj).isNullObject();
      }
      return JSONNull.getInstance()
            .equalsobj );
   }

   /**
    * Tests if Class represents a primitive number or wrapper.<br>
    */
   public static boolean isNumberClass clazz ) {
      return clazz != null
            && (Byte.TYPE.isAssignableFromclazz || Short.TYPE.isAssignableFromclazz )
                  || Integer.TYPE.isAssignableFromclazz || Long.TYPE.isAssignableFromclazz )
                  || Float.TYPE.isAssignableFromclazz || Double.TYPE.isAssignableFromclazz || Number.class.isAssignableFromclazz ));
   }

   /**
    * Tests if obj is a primitive number or wrapper.<br>
    */
   public static boolean isNumberObject obj ) {
      if( (obj != null && obj.getClass() == Byte.TYPE)
            || (obj != null && obj.getClass() == Short.TYPE)
            || (obj != null && obj.getClass() == Integer.TYPE)
            || (obj != null && obj.getClass() == Long.TYPE)
            || (obj != null && obj.getClass() == Float.TYPE)
            || (obj != null && obj.getClass() == Double.TYPE) ){
         return true;
      }

      return obj instanceof Number;
   }

   /**
    * Tests if obj is not a boolean, number, string or array.
    */
   public static boolean isObjectObject obj ) {
      return !isNumberobj && !isStringobj && !isBooleanobj && !isArrayobj )
            && !isFunctionobj || isNullobj );
   }

   /**
    * Tests if Class represents a String or a char
    */
   public static boolean isStringClass clazz ) {
      return clazz != null
            && (String.class.isAssignableFromclazz || (Character.TYPE.isAssignableFromclazz || Character.class.isAssignableFromclazz )));
   }

   /**
    * Tests if obj is a String or a char
    */
   public static boolean isStringObject obj ) {
      if( (obj instanceof String)
            || (obj instanceof Character)
            || (obj != null && (obj.getClass() == Character.TYPE || String.class.isAssignableFromobj.getClass() ))) ){
         return true;
      }
      return false;
   }

   /**
    * Tests if the String possibly represents a valid JSON String.<br>
    * Valid JSON strings are:
    <ul>
    <li>"null"</li>
    <li>starts with "[" and ends with "]"</li>
    <li>starts with "{" and ends with "}"</li>
    </ul>
    */
   public static boolean mayBeJSONString string ) {
      return string != null
            && ("null".equalsstring )
                  || (string.startsWith"[" && string.endsWith"]" )) || (string.startsWith"{" && string.endsWith"}" )));
   }

   /**
    * Creates a new MorphDynaBean from a JSONObject. The MorphDynaBean will have
    * all the properties of the original JSONObject with the most accurate type.
    * Values of properties are not copied.
    */
   public static DynaBean newDynaBeanJSONObject jsonObject ) {
      return newDynaBeanjsonObject, new JsonConfig() );
   }

   /**
    * Creates a new MorphDynaBean from a JSONObject. The MorphDynaBean will have
    * all the properties of the original JSONObject with the most accurate type.
    * Values of properties are not copied.
    */
   public static DynaBean newDynaBeanJSONObject jsonObject, JsonConfig jsonConfig ) {
      Map props = getPropertiesjsonObject );
      forIterator entries = props.entrySet()
            .iterator(); entries.hasNext()){
         Map.Entry entry = (Map.Entryentries.next();
         String key = (Stringentry.getKey();
         if!JSONUtils.isJavaIdentifierkey ) ){
            String parsedKey = JSONUtils.convertToJavaIdentifierkey, jsonConfig );
            ifparsedKey.compareTokey != ){
               props.putparsedKey, props.removekey ) );
            }
         }
      }
      MorphDynaClass dynaClass = new MorphDynaClassprops );
      MorphDynaBean dynaBean = null;
      try{
         dynaBean = (MorphDynaBeandynaClass.newInstance();
         dynaBean.setDynaBeanClassdynaClass );
      }catchException e ){
         throw new JSONException);
      }
      return dynaBean;
   }

   /**
    * Produce a string from a Number.
    *
    @param n A Number
    @return A String.
    @throws JSONException If n is a non-finite number.
    */
   public static String numberToStringNumber n ) {
      ifn == null ){
         throw new JSONException"Null pointer" );
      }
      testValidity);

      // Shave off trailing zeros and decimal point, if possible.

      String s = n.toString();
      ifs.indexOf'.' && s.indexOf'e' && s.indexOf'E' ){
         whiles.endsWith"0" ) ){
            s = s.substring0, s.length() );
         }
         ifs.endsWith"." ) ){
            s = s.substring0, s.length() );
         }
      }
      return s;
   }

   /**
    * Produce a string in double quotes with backslash sequences in all the
    * right places. A backslash will be inserted within </, allowing JSON text
    * to be delivered in HTML. In JSON text, a string cannot contain a control
    * character or an unescaped quote or backslash.<br>
    <strong>CAUTION:</strong> if <code>string</code> represents a
    * javascript function, translation of characters will not take place. This
    * will produce a non-conformant JSON text.
    *
    @param string A String
    @return A String correctly formatted for insertion in a JSON text.
    */
   public static String quoteString string ) {
      ifisFunctionstring ) ) {
         return string;
      }
      ifstring == null || string.length() == ) {
         return "\"\"";
      }

      char b;
      char c = 0;
      int i;
      int len = string.length();
      StringBuffer sb = new StringBufferlen * );
      String t;
      char[] chars = string.toCharArray();
      char[] buffer = new char[1030];
      int bufferIndex = 0;
      sb.append'"' );
      fori = 0; i < len; i += ) {
         ifbufferIndex > 1024 ) {
            sb.appendbuffer, 0, bufferIndex );
            bufferIndex = 0;
         }
         b = c;
         c = chars[i];
         switch) {
            case '\\':
            case '"':
               buffer[bufferIndex++'\\';
               buffer[bufferIndex++= c;
               break;
            case '/':
               ifb == '<' ) {
                  buffer[bufferIndex++'\\';
               }
               buffer[bufferIndex++= c;
               break;
            default:
               ifc < ' ' ) {
                  switch) {
                     case '\b':
                        buffer[bufferIndex++'\\';
                        buffer[bufferIndex++'b';
                        break;
                     case '\t':
                        buffer[bufferIndex++'\\';
                        buffer[bufferIndex++'t';
                        break;
                     case '\n':
                        buffer[bufferIndex++'\\';
                        buffer[bufferIndex++'n';
                        break;
                     case '\f':
                        buffer[bufferIndex++'\\';
                        buffer[bufferIndex++'f';
                        break;
                     case '\r':
                        buffer[bufferIndex++'\\';
                        buffer[bufferIndex++'r';
                        break;
                     default:
                        t = "000" + Integer.toHexString);
                        int tLength = t.length();
                        buffer[bufferIndex++'\\';
                        buffer[bufferIndex++'u';
                        buffer[bufferIndex++= t.charAttLength - );
                        buffer[bufferIndex++= t.charAttLength - );
                        buffer[bufferIndex++= t.charAttLength - );
                        buffer[bufferIndex++= t.charAttLength - );
                  }
               else {
                  buffer[bufferIndex++= c;
               }
         }
      }
      sb.appendbuffer, 0, bufferIndex );
      sb.append'"' );
      return sb.toString();
   }

   /**
    * Strips any single-quotes or double-quotes from both sides of the string.
    */
   public static String stripQuotesString input ) {
      ifinput.length() ){
         return input;
      }else ifinput.startsWithSINGLE_QUOTE && input.endsWithSINGLE_QUOTE ) ){
         return input.substring1, input.length() );
      }else ifinput.startsWithDOUBLE_QUOTE && input.endsWithDOUBLE_QUOTE ) ){
         return input.substring1, input.length() );
      }else{
         return input;
      }
   }

   /**
    * Returns true if the input has single-quotes or double-quotes at both sides.
    */
   public static boolean hasQuotesString input ) {
      ifinput == null || input.length() ){
         return false;
      }
      return input.startsWithSINGLE_QUOTE && input.endsWithSINGLE_QUOTE ||
         input.startsWithDOUBLE_QUOTE && input.endsWithDOUBLE_QUOTE );
   }
   
   public static boolean isJsonKeywordString input, JsonConfig jsonConfig ) {
      ifinput == null ){
         return false;
      }
      return "null".equalsinput ||
              "true".equalsinput ||
              "false".equalsinput ||
              (jsonConfig.isJavascriptCompliant() && "undefined".equalsinput ));
   }
   
   /**
    * Throw an exception if the object is an NaN or infinite number.
    *
    @param o The object to test.
    @throws JSONException If o is a non-finite number.
    */
   public static void testValidityObject o ) {
      ifo != null ){
         ifinstanceof Double ){
            if( ((Doubleo).isInfinite() || ((Doubleo).isNaN() ){
               throw new JSONException"JSON does not allow non-finite numbers" );
            }
         }else ifinstanceof Float ){
            if( ((Floato).isInfinite() || ((Floato).isNaN() ){
               throw new JSONException"JSON does not allow non-finite numbers." );
            }
         }else ifinstanceof BigDecimal || o instanceof BigInteger ){
            // ok
            return;
         }
      }
   }

   /**
    * Transforms a Number into a valid javascript number.<br>
    * Float gets promoted to Double.<br>
    * Byte and Short get promoted to Integer.<br>
    * Long gets downgraded to Integer if possible.<br>
    */
   public static Number transformNumberNumber input ) {
      ifinput instanceof Float ){
         return new Doubleinput.toString() );
      }else ifinput instanceof Short ){
         return new Integerinput.intValue() );
      }else ifinput instanceof Byte ){
         return new Integerinput.intValue() );
      }else ifinput instanceof Long ){
         Long max = new LongInteger.MAX_VALUE );
         ifinput.longValue() <= max.longValue() && input.longValue() >= Integer.MIN_VALUE ){
            return new Integerinput.intValue() );
         }
      }

      return input;
   }

   /**
    * Make a JSON text of an Object value. If the object has an
    * value.toJSONString() method, then that method will be used to produce the
    * JSON text. The method is required to produce a strictly conforming text.
    * If the object does not contain a toJSONString method (which is the most
    * common case), then a text will be produced by the rules.
    <p>
    * Warning: This method assumes that the data structure is acyclical.
    *
    @param value The value to be serialized.
    @return a printable, displayable, transmittable representation of the
    *         object, beginning with <code>{</code>&nbsp;<small>(left brace)</small>
    *         and ending with <code>}</code>&nbsp;<small>(right brace)</small>.
    @throws JSONException If the value is or contains an invalid number.
    */
   public static String valueToStringObject value ) {
      ifvalue == null || isNullvalue ) ){
         return "null";
      }
      ifvalue instanceof JSONFunction ){
         return ((JSONFunctionvalue).toString();
      }
      ifvalue instanceof JSONString ){
         Object o;
         try{
            o = ((JSONStringvalue).toJSONString();
         }catchException e ){
            throw new JSONException);
         }
         ifinstanceof String ){
            return (Stringo;
         }
         throw new JSONException"Bad value from toJSONString: " + o );
      }
      ifvalue instanceof Number ){
         return numberToString( (Numbervalue );
      }
      ifvalue instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray ){
         return value.toString();
      }
      return quotevalue.toString() );
   }

   /**
    * Make a prettyprinted JSON text of an object value.
    <p>
    * Warning: This method assumes that the data structure is acyclical.
    *
    @param value The value to be serialized.
    @param indentFactor The number of spaces to add to each level of
    *        indentation.
    @param indent The indentation of the top level.
    @return a printable, displayable, transmittable representation of the
    *         object, beginning with <code>{</code>&nbsp;<small>(left brace)</small>
    *         and ending with <code>}</code>&nbsp;<small>(right brace)</small>.
    @throws JSONException If the object contains an invalid number.
    */
   public static String valueToStringObject value, int indentFactor, int indent ) {
      ifvalue == null || isNullvalue ) ){
         return "null";
      }
      ifvalue instanceof JSONFunction ){
         return ((JSONFunctionvalue).toString();
      }
      ifvalue instanceof JSONString ){
         return ((JSONStringvalue).toJSONString();
      }
      ifvalue instanceof Number ){
         return numberToString( (Numbervalue );
      }
      ifvalue instanceof Boolean ){
         return value.toString();
      }
      ifvalue instanceof JSONObject ){
         return ((JSONObjectvalue).toStringindentFactor, indent );
      }
      ifvalue instanceof JSONArray ){
         return ((JSONArrayvalue).toStringindentFactor, indent );
      }
      return quotevalue.toString() );
   }

   /**
    * Finds out if n represents a BigInteger
    *
    @return true if n is instanceOf BigInteger or the literal value can be
    *         evaluated as a BigInteger
    */
   private static boolean isBigDecimalNumber n ) {
      ifinstanceof BigDecimal ){
         return true;
      }
      try{
         new BigDecimalString.valueOf) );
         return true;
      }catchNumberFormatException e ){
         return false;
      }
   }

   /**
    * Finds out if n represents a BigInteger
    *
    @return true if n is instanceOf BigInteger or the literal value can be
    *         evaluated as a BigInteger
    */
   private static boolean isBigIntegerNumber n ) {
      ifinstanceof BigInteger ){
         return true;
      }
      try{
         new BigIntegerString.valueOf) );
         return true;
      }catchNumberFormatException e ){
         return false;
      }
   }

   /**
    * Finds out if n represents a Double.
    *
    @return true if n is instanceOf Double or the literal value can be
    *         evaluated as a Double.
    */
   private static boolean isDoubleNumber n ) {
      ifinstanceof Double ){
         return true;
      }
      try{
         double d = Double.parseDoubleString.valueOf) );
         return !Double.isInfinite);
      }catchNumberFormatException e ){
         return false;
      }
   }

   /**
    * Finds out if n represents a Float.
    *
    @return true if n is instanceOf Float or the literal value can be
    *         evaluated as a Float.
    */
   private static boolean isFloatNumber n ) {
      ifinstanceof Float ){
         return true;
      }
      try{
         float f = Float.parseFloatString.valueOf) );
         return !Float.isInfinite);
      }catchNumberFormatException e ){
         return false;
      }
   }

   /**
    * Finds out if n represents an Integer.
    *
    @return true if n is instanceOf Integer or the literal value can be
    *         evaluated as an Integer.
    */
   private static boolean isIntegerNumber n ) {
      ifinstanceof Integer ){
         return true;
      }
      try{
         Integer.parseIntString.valueOf) );
         return true;
      }catchNumberFormatException e ){
         return false;
      }
   }

   /**
    * Finds out if n represents a Long.
    *
    @return true if n is instanceOf Long or the literal value can be evaluated
    *         as a Long.
    */
   private static boolean isLongNumber n ) {
      ifinstanceof Long ){
         return true;
      }
      try{
         Long.parseLongString.valueOf) );
         return true;
      }catchNumberFormatException e ){
         return false;
      }
   }

   private JSONUtils() {
      super();
   }
}