Home » tapestry-src-5.0.19 » org.apache.tapestry5.json » [javadoc | source]

    1   // Copyright 2007 The Apache Software Foundation
    2   //
    3   // Licensed under the Apache License, Version 2.0 (the "License");
    4   // you may not use this file except in compliance with the License.
    5   // You may obtain a copy of the License at
    6   //
    7   //     http://www.apache.org/licenses/LICENSE-2.0
    8   //
    9   // Unless required by applicable law or agreed to in writing, software
   10   // distributed under the License is distributed on an "AS IS" BASIS,
   11   // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   12   // See the License for the specific language governing permissions and
   13   // limitations under the License.
   14   
   15   package org.apache.tapestry5.json;
   16   
   17   /*
   18    Copyright (c) 2002 JSON.org
   19   
   20    Permission is hereby granted, free of charge, to any person obtaining a copy
   21    of this software and associated documentation files (the "Software"), to deal
   22    in the Software without restriction, including without limitation the rights
   23    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   24    copies of the Software, and to permit persons to whom the Software is
   25    furnished to do so, subject to the following conditions:
   26   
   27    The above copyright notice and this permission notice shall be included in all
   28    copies or substantial portions of the Software.
   29   
   30    The Software shall be used for Good, not Evil.
   31   
   32    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   33    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   34    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   35    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   36    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   37    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   38    SOFTWARE.
   39    */
   40   
   41   import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
   42   
   43   import java.util.Map;
   44   import java.util.Set;
   45   
   46   /**
   47    * A JSONObject is an unordered collection of name/value pairs. Its external form is a string wrapped in curly braces
   48    * with colons between the names and values, and commas between the values and names. The internal form is an object
   49    * having <code>get</code> and <code>opt</code> methods for accessing the values by name, and <code>put</code> methods
   50    * for adding or replacing values by name. The values can be any of these types: <code>Boolean</code>,
   51    * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>, <code>String</code>, or the
   52    * <code>JSONObject.NULL</code> object. A JSONObject constructor can be used to convert an external form JSON text into
   53    * an internal form whose values can be retrieved with the <code>get</code> and <code>opt</code> methods, or to convert
   54    * values into a JSON text using the <code>put</code> and <code>toString</code> methods. A <code>get</code> method
   55    * returns a value if one can be found, and throws an exception if one cannot be found. An <code>opt</code> method
   56    * returns a default value instead of throwing an exception, and so is useful for obtaining optional values.
   57    * <p/>
   58    * The generic <code>get()</code> and <code>opt()</code> methods return an object, which you can cast or query for type.
   59    * There are also typed <code>get</code> and <code>opt</code> methods that do type checking and type coersion for you.
   60    * <p/>
   61    * The <code>put</code> methods adds values to an object. For example,
   62    * <p/>
   63    * <pre>
   64    * myString = new JSONObject().put(&quot;JSON&quot;, &quot;Hello, World!&quot;).toString();
   65    * </pre>
   66    * <p/>
   67    * produces the string <code>{"JSON": "Hello, World"}</code>.
   68    * <p/>
   69    * The texts produced by the <code>toString</code> methods strictly conform to the JSON sysntax rules. The constructors
   70    * are more forgiving in the texts they will accept: <ul> <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may
   71    * appear just before the closing brace.</li> <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
   72    * quote)</small>.</li> <li>Strings do not need to be quoted at all if they do not begin with a quote or single quote,
   73    * and if they do not contain leading or trailing spaces, and if they do not contain any of these characters: <code>{ }
   74    * [ ] / \ : , = ; #</code> and if they do not look like numbers and if they are not the reserved words
   75    * <code>true</code>, <code>false</code>, or <code>null</code>.</li> <li>Keys can be followed by <code>=</code> or
   76    * <code>=></code> as well as by <code>:</code>.</li> <li>Values can be followed by <code>;</code>
   77    * <small>(semicolon)</small> as well as by <code>,</code> <small>(comma)</small>.</li> <li>Numbers may have the
   78    * <code>0-</code> <small>(octal)</small> or <code>0x-</code> <small>(hex)</small> prefix.</li> <li>Comments written in
   79    * the slashshlash, slashstar, and hash conventions will be ignored.</li> </ul> <hr/>
   80    * <p/>
   81    * This class, and the other related classes, have been heavily modified from the original source, to fit Tapestry
   82    * standards and to make use of JDK 1.5 features such as generics. Further, since the interest of Tapestry is primarily
   83    * constructing JSON (and not parsing it), many of the non-essential methods have been removed (since the original code
   84    * came with no tests).
   85    *
   86    * @author JSON.org
   87    * @version 2
   88    */
   89   @SuppressWarnings({ "CloneDoesntCallSuperClone" })
   90   public final class JSONObject
   91   {
   92   
   93       /**
   94        * JSONObject.NULL is equivalent to the value that JavaScript calls null, whilst Java's null is equivalent to the
   95        * value that JavaScript calls undefined.
   96        */
   97       private static final class Null
   98       {
   99   
  100           /**
  101            * There is only intended to be a single instance of the NULL object, so the clone method returns itself.
  102            *
  103            * @return NULL.
  104            */
  105           @Override
  106           protected final Object clone()
  107           {
  108               return this;
  109           }
  110   
  111           /**
  112            * A Null object is equal to the null value and to itself.
  113            *
  114            * @param object An object to test for nullness.
  115            * @return true if the object parameter is the JSONObject.NULL object or null.
  116            */
  117           @Override
  118           public boolean equals(Object object)
  119           {
  120               return object == null || object == this;
  121           }
  122   
  123           /**
  124            * Get the "null" string value.
  125            *
  126            * @return The string "null".
  127            */
  128           @Override
  129           public String toString()
  130           {
  131               return "null";
  132           }
  133       }
  134   
  135       /**
  136        * The map where the JSONObject's properties are kept.
  137        */
  138       private final Map<String, Object> properties = CollectionFactory.newMap();
  139   
  140       /**
  141        * It is sometimes more convenient and less ambiguous to have a <code>NULL</code> object than to use Java's
  142        * <code>null</code> value. <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
  143        * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
  144        */
  145       public static final Object NULL = new Null();
  146   
  147       /**
  148        * Construct an empty JSONObject.
  149        */
  150       public JSONObject()
  151       {
  152       }
  153   
  154       /**
  155        * Construct a JSONObject from a subset of another JSONObject. An array of strings is used to identify the keys that
  156        * should be copied. Missing keys are ignored.
  157        *
  158        * @param source        A JSONObject.
  159        * @param propertyNames The strings to copy.
  160        * @throws RuntimeException If a value is a non-finite number.
  161        */
  162       public JSONObject(JSONObject source, String... propertyNames)
  163       {
  164           for (String name : propertyNames)
  165           {
  166               Object value = source.opt(name);
  167   
  168               if (value != null) put(name, value);
  169           }
  170       }
  171   
  172       /**
  173        * Construct a JSONObject from a JSONTokener.
  174        *
  175        * @param x A JSONTokener object containing the source string. @ If there is a syntax error in the source string.
  176        */
  177       JSONObject(JSONTokener x)
  178       {
  179           String key;
  180   
  181           if (x.nextClean() != '{')
  182           {
  183               throw x.syntaxError("A JSONObject text must begin with '{'");
  184           }
  185   
  186           while (true)
  187           {
  188               char c = x.nextClean();
  189               switch (c)
  190               {
  191                   case 0:
  192                       throw x.syntaxError("A JSONObject text must end with '}'");
  193                   case '}':
  194                       return;
  195                   default:
  196                       x.back();
  197                       key = x.nextValue().toString();
  198               }
  199   
  200               /*
  201                * The key is followed by ':'. We will also tolerate '=' or '=>'.
  202                */
  203   
  204               c = x.nextClean();
  205               if (c == '=')
  206               {
  207                   if (x.next() != '>')
  208                   {
  209                       x.back();
  210                   }
  211               }
  212               else if (c != ':')
  213               {
  214                   throw x.syntaxError("Expected a ':' after a key");
  215               }
  216               put(key, x.nextValue());
  217   
  218               /*
  219                * Pairs are separated by ','. We will also tolerate ';'.
  220                */
  221   
  222               switch (x.nextClean())
  223               {
  224                   case ';':
  225                   case ',':
  226                       if (x.nextClean() == '}')
  227                       {
  228                           return;
  229                       }
  230                       x.back();
  231                       break;
  232                   case '}':
  233                       return;
  234                   default:
  235                       throw x.syntaxError("Expected a ',' or '}'");
  236               }
  237           }
  238       }
  239   
  240       /**
  241        * Construct a JSONObject from a string. This is the most commonly used JSONObject constructor.
  242        *
  243        * @param string A string beginning with <code>{</code>&nbsp;<small>(left brace)</small> and ending with
  244        *               <code>}</code>&nbsp;<small>(right brace)</small>.
  245        * @throws RuntimeException If there is a syntax error in the source string.
  246        */
  247       public JSONObject(String string)
  248       {
  249           this(new JSONTokener(string));
  250       }
  251   
  252       /**
  253        * Accumulate values under a key. It is similar to the put method except that if there is already an object stored
  254        * under the key then a JSONArray is stored under the key to hold all of the accumulated values. If there is already
  255        * a JSONArray, then the new value is appended to it. In contrast, the put method replaces the previous value.
  256        *
  257        * @param key   A key string.
  258        * @param value An object to be accumulated under the key.
  259        * @return this.
  260        * @throws {@link RuntimeException} If the value is an invalid number or if the key is null.
  261        */
  262       public JSONObject accumulate(String key, Object value)
  263       {
  264           testValidity(value);
  265   
  266           Object existing = opt(key);
  267   
  268           if (existing == null)
  269           {
  270               // Note that the original implementation of this method contradicited the method
  271               // documentation.
  272               put(key, value);
  273               return this;
  274           }
  275   
  276           if (existing instanceof JSONArray)
  277           {
  278               ((JSONArray) existing).put(value);
  279               return this;
  280           }
  281   
  282           // Replace the existing value, of any type, with an array that includes both the
  283           // existing and the new value.
  284   
  285           put(key, new JSONArray().put(existing).put(value));
  286   
  287           return this;
  288       }
  289   
  290       /**
  291        * Append values to the array under a key. If the key does not exist in the JSONObject, then the key is put in the
  292        * JSONObject with its value being a JSONArray containing the value parameter. If the key was already associated
  293        * with a JSONArray, then the value parameter is appended to it.
  294        *
  295        * @param key   A key string.
  296        * @param value An object to be accumulated under the key.
  297        * @return this. @ If the key is null or if the current value associated with the key is not a JSONArray.
  298        */
  299       public JSONObject append(String key, Object value)
  300       {
  301           testValidity(value);
  302           Object o = opt(key);
  303           if (o == null)
  304           {
  305               put(key, new JSONArray().put(value));
  306           }
  307           else if (o instanceof JSONArray)
  308           {
  309               put(key, ((JSONArray) o).put(value));
  310           }
  311           else
  312           {
  313               throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray.");
  314           }
  315   
  316           return this;
  317       }
  318   
  319       /**
  320        * Produce a string from a double. The string "null" will be returned if the number is not finite.
  321        *
  322        * @param d A double.
  323        * @return A String.
  324        */
  325       static String doubleToString(double d)
  326       {
  327           if (Double.isInfinite(d) || Double.isNaN(d))
  328           {
  329               return "null";
  330           }
  331   
  332           // Shave off trailing zeros and decimal point, if possible.
  333   
  334           String s = Double.toString(d);
  335           if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0)
  336           {
  337               while (s.endsWith("0"))
  338               {
  339                   s = s.substring(0, s.length() - 1);
  340               }
  341               if (s.endsWith("."))
  342               {
  343                   s = s.substring(0, s.length() - 1);
  344               }
  345           }
  346           return s;
  347       }
  348   
  349       /**
  350        * Get the value object associated with a key.
  351        *
  352        * @param key A key string.
  353        * @return The object associated with the key. @ if the key is not found.
  354        * @see #opt(String)
  355        */
  356       public Object get(String key)
  357       {
  358           Object o = opt(key);
  359           if (o == null)
  360           {
  361               throw new RuntimeException("JSONObject[" + quote(key) + "] not found.");
  362           }
  363   
  364           return o;
  365       }
  366   
  367       /**
  368        * Get the boolean value associated with a key.
  369        *
  370        * @param key A key string.
  371        * @return The truth.
  372        * @throws RuntimeException if the value is not a Boolean or the String "true" or "false".
  373        */
  374       public boolean getBoolean(String key)
  375       {
  376           Object o = get(key);
  377   
  378           if (o instanceof Boolean) return o.equals(Boolean.TRUE);
  379   
  380           if (o instanceof String)
  381           {
  382               String value = (String) o;
  383   
  384               if (value.equalsIgnoreCase("true")) return true;
  385   
  386               if (value.equalsIgnoreCase("false")) return false;
  387           }
  388   
  389           throw new RuntimeException("JSONObject[" + quote(key) + "] is not a Boolean.");
  390       }
  391   
  392       /**
  393        * Get the double value associated with a key.
  394        *
  395        * @param key A key string.
  396        * @return The numeric value. @ if the key is not found or if the value is not a Number object and cannot be
  397        *         converted to a number.
  398        */
  399       public double getDouble(String key)
  400       {
  401           Object value = get(key);
  402   
  403           try
  404           {
  405               if (value instanceof Number) return ((Number) value).doubleValue();
  406   
  407               // This is a bit sloppy for the case where value is not a string.
  408   
  409               return Double.valueOf((String) value);
  410           }
  411           catch (Exception e)
  412           {
  413               throw new RuntimeException("JSONObject[" + quote(key) + "] is not a number.");
  414           }
  415       }
  416   
  417       /**
  418        * Get the int value associated with a key. If the number value is too large for an int, it will be clipped.
  419        *
  420        * @param key A key string.
  421        * @return The integer value. @ if the key is not found or if the value cannot be converted to an integer.
  422        */
  423       public int getInt(String key)
  424       {
  425           Object value = get(key);
  426   
  427           if (value instanceof Number) return ((Number) value).intValue();
  428   
  429           // Very inefficient way to do this!
  430           return (int) getDouble(key);
  431       }
  432   
  433       /**
  434        * Get the JSONArray value associated with a key.
  435        *
  436        * @param key A key string.
  437        * @return A JSONArray which is the value.
  438        * @throws RuntimeException if the key is not found or if the value is not a JSONArray.
  439        */
  440       public JSONArray getJSONArray(String key)
  441       {
  442           Object o = get(key);
  443           if (o instanceof JSONArray)
  444           {
  445               return (JSONArray) o;
  446           }
  447   
  448           throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray.");
  449       }
  450   
  451       /**
  452        * Get the JSONObject value associated with a key.
  453        *
  454        * @param key A key string.
  455        * @return A JSONObject which is the value.
  456        * @throws RuntimeException if the key is not found or if the value is not a JSONObject.
  457        */
  458       public JSONObject getJSONObject(String key)
  459       {
  460           Object o = get(key);
  461           if (o instanceof JSONObject)
  462           {
  463               return (JSONObject) o;
  464           }
  465   
  466           throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONObject.");
  467       }
  468   
  469       /**
  470        * Get the long value associated with a key. If the number value is too long for a long, it will be clipped.
  471        *
  472        * @param key A key string.
  473        * @return The long value. @ if the key is not found or if the value cannot be converted to a long.
  474        */
  475       public long getLong(String key)
  476       {
  477           Object o = get(key);
  478           return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(key);
  479       }
  480   
  481       /**
  482        * Get the string associated with a key.
  483        *
  484        * @param key A key string.
  485        * @return A string which is the value.
  486        * @throws RuntimeException if the key is not found.
  487        */
  488       public String getString(String key)
  489       {
  490           return get(key).toString();
  491       }
  492   
  493       /**
  494        * Determine if the JSONObject contains a specific key.
  495        *
  496        * @param key A key string.
  497        * @return true if the key exists in the JSONObject.
  498        */
  499       public boolean has(String key)
  500       {
  501           return properties.containsKey(key);
  502       }
  503   
  504       /**
  505        * Determine if the value associated with the key is null or if there is no value.
  506        *
  507        * @param key A key string.
  508        * @return true if there is no value associated with the key or if the value is the JSONObject.NULL object.
  509        */
  510       public boolean isNull(String key)
  511       {
  512           return JSONObject.NULL.equals(opt(key));
  513       }
  514   
  515       /**
  516        * Get an enumeration of the keys of the JSONObject. Caution: the set should not be modified.
  517        *
  518        * @return An iterator of the keys.
  519        */
  520       public Set<String> keys()
  521       {
  522           return properties.keySet();
  523       }
  524   
  525       /**
  526        * Get the number of keys stored in the JSONObject.
  527        *
  528        * @return The number of keys in the JSONObject.
  529        */
  530       public int length()
  531       {
  532           return properties.size();
  533       }
  534   
  535       /**
  536        * Produce a JSONArray containing the names of the elements of this JSONObject.
  537        *
  538        * @return A JSONArray containing the key strings, or null if the JSONObject is empty.
  539        */
  540       public JSONArray names()
  541       {
  542           JSONArray ja = new JSONArray();
  543   
  544           for (String key : keys())
  545           {
  546               ja.put(key);
  547           }
  548   
  549           return ja.length() == 0 ? null : ja;
  550       }
  551   
  552       /**
  553        * Produce a string from a Number.
  554        *
  555        * @param n A Number
  556        * @return A String. @ If n is a non-finite number.
  557        */
  558       static String numberToString(Number n)
  559       {
  560           assert n != null;
  561   
  562           testValidity(n);
  563   
  564           // Shave off trailing zeros and decimal point, if possible.
  565   
  566           String s = n.toString();
  567           if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0)
  568           {
  569               while (s.endsWith("0"))
  570               {
  571                   s = s.substring(0, s.length() - 1);
  572               }
  573               if (s.endsWith("."))
  574               {
  575                   s = s.substring(0, s.length() - 1);
  576               }
  577           }
  578           return s;
  579       }
  580   
  581       /**
  582        * Get an optional value associated with a key.
  583        *
  584        * @param key A key string.
  585        * @return An object which is the value, or null if there is no value.
  586        * @see #get(String)
  587        */
  588       public Object opt(String key)
  589       {
  590           return properties.get(key);
  591       }
  592   
  593       /**
  594        * Put a key/value pair in the JSONObject. If the value is null, then the key will be removed from the JSONObject if
  595        * it is present.
  596        *
  597        * @param key   A key string.
  598        * @param value An object which is the value. It should be of one of these types: Boolean, Double, Integer,
  599        *              JSONArray, JSONObject, Long, String, or the JSONObject.NULL object.
  600        * @return this.
  601        * @throws RuntimeException If the value is non-finite number or if the key is null.
  602        */
  603       public JSONObject put(String key, Object value)
  604       {
  605           assert key != null;
  606   
  607           if (value != null)
  608           {
  609               testValidity(value);
  610               properties.put(key, value);
  611           }
  612           else
  613           {
  614               remove(key);
  615           }
  616   
  617           return this;
  618       }
  619   
  620       /**
  621        * Produce a string in double quotes with backslash sequences in all the right places. A backslash will be inserted
  622        * within </, allowing JSON text to be delivered in HTML. In JSON text, a string cannot contain a control character
  623        * or an unescaped quote or backslash.
  624        *
  625        * @param string A String
  626        * @return A String correctly formatted for insertion in a JSON text.
  627        */
  628       public static String quote(String string)
  629       {
  630           if (string == null || string.length() == 0)
  631           {
  632               return "\"\"";
  633           }
  634   
  635           char b;
  636           char c = 0;
  637           int i;
  638           int len = string.length();
  639           StringBuilder buffer = new StringBuilder(len + 4);
  640           String t;
  641   
  642           buffer.append('"');
  643           for (i = 0; i < len; i += 1)
  644           {
  645               b = c;
  646               c = string.charAt(i);
  647               switch (c)
  648               {
  649                   case '\\':
  650                   case '"':
  651                       buffer.append('\\');
  652                       buffer.append(c);
  653                       break;
  654                   case '/':
  655                       if (b == '<')
  656                       {
  657                           buffer.append('\\');
  658                       }
  659                       buffer.append(c);
  660                       break;
  661                   case '\b':
  662                       buffer.append("\\b");
  663                       break;
  664                   case '\t':
  665                       buffer.append("\\t");
  666                       break;
  667                   case '\n':
  668                       buffer.append("\\n");
  669                       break;
  670                   case '\f':
  671                       buffer.append("\\f");
  672                       break;
  673                   case '\r':
  674                       buffer.append("\\r");
  675                       break;
  676                   default:
  677                       if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100'))
  678                       {
  679                           t = "000" + Integer.toHexString(c);
  680                           buffer.append("\\u").append(t.substring(t.length() - 4));
  681                       }
  682                       else
  683                       {
  684                           buffer.append(c);
  685                       }
  686               }
  687           }
  688           buffer.append('"');
  689           return buffer.toString();
  690       }
  691   
  692       /**
  693        * Remove a name and its value, if present.
  694        *
  695        * @param key The name to be removed.
  696        * @return The value that was associated with the name, or null if there was no value.
  697        */
  698       public Object remove(String key)
  699       {
  700           return properties.remove(key);
  701       }
  702   
  703       private static final Class[] ALLOWED = new Class[] { String.class, Boolean.class, Number.class, JSONObject.class,
  704               JSONArray.class, Null.class };
  705   
  706       /**
  707        * Throw an exception if the object is an NaN or infinite number, or not a type which may be stored.
  708        *
  709        * @param value The object to test. @ If o is a non-finite number.
  710        */
  711       @SuppressWarnings("unchecked")
  712       static void testValidity(Object value)
  713       {
  714           if (value == null) return;
  715   
  716           boolean found = false;
  717           Class actual = value.getClass();
  718   
  719           for (Class allowed : ALLOWED)
  720           {
  721               if (allowed.isAssignableFrom(actual))
  722               {
  723                   found = true;
  724                   break;
  725               }
  726           }
  727   
  728           if (!found) throw new RuntimeException(String
  729                   .format(
  730                   "JSONObject properties may be String, Boolean, Number, JSONObject or JSONArray. Type %s is not allowed.",
  731                   actual.getName()));
  732   
  733           if (value instanceof Double)
  734           {
  735               Double asDouble = (Double) value;
  736   
  737               if (asDouble.isInfinite() || asDouble.isNaN())
  738               {
  739                   throw new RuntimeException("JSON does not allow non-finite numbers.");
  740               }
  741   
  742               return;
  743           }
  744   
  745           if (value instanceof Float)
  746           {
  747               Float asFloat = (Float) value;
  748   
  749               if (asFloat.isInfinite() || asFloat.isNaN())
  750               {
  751                   throw new RuntimeException("JSON does not allow non-finite numbers.");
  752               }
  753   
  754           }
  755   
  756       }
  757   
  758       /**
  759        * Make a JSON text of this JSONObject. For compactness, no whitespace is added. If this would not result in a
  760        * syntactically correct JSON text, then null will be returned instead.
  761        * <p/>
  762        * Warning: This method assumes that the data structure is acyclical.
  763        *
  764        * @return a printable, displayable, portable, transmittable representation of the object, beginning with
  765        *         <code>{</code>&nbsp;<small>(left brace)</small> and ending with <code>}</code>&nbsp;<small>(right
  766        *         brace)</small>.
  767        */
  768       @Override
  769       public String toString()
  770       {
  771           boolean comma = false;
  772   
  773           StringBuilder buffer = new StringBuilder("{");
  774   
  775           for (String key : keys())
  776           {
  777               if (comma) buffer.append(',');
  778   
  779               buffer.append(quote(key));
  780               buffer.append(':');
  781               buffer.append(valueToString(properties.get(key)));
  782   
  783               comma = true;
  784           }
  785   
  786           buffer.append('}');
  787   
  788           return buffer.toString();
  789       }
  790   
  791       /**
  792        * Make a JSON text of an Object value. If the object has an value.toJSONString() method, then that method will be
  793        * used to produce the JSON text. The method is required to produce a strictly conforming text. If the object does
  794        * not contain a toJSONString method (which is the most common case), then a text will be produced by the rules.
  795        * <p/>
  796        * Warning: This method assumes that the data structure is acyclical.
  797        *
  798        * @param value The value to be serialized.
  799        * @return a printable, displayable, transmittable representation of the object, beginning with
  800        *         <code>{</code>&nbsp;<small>(left brace)</small> and ending with <code>}</code>&nbsp;<small>(right
  801        *         brace)</small>. @ If the value is or contains an invalid number.
  802        */
  803       static String valueToString(Object value)
  804       {
  805           if (value == null || value.equals(null))
  806           {
  807               return "null";
  808           }
  809   
  810           if (value instanceof JSONString)
  811           {
  812               try
  813               {
  814                   String json = ((JSONString) value).toJSONString();
  815   
  816                   return quote(json);
  817               }
  818               catch (Exception e)
  819               {
  820                   throw new RuntimeException(e);
  821               }
  822   
  823           }
  824   
  825           if (value instanceof Number)
  826           {
  827               return numberToString((Number) value);
  828           }
  829   
  830           if (value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray)
  831           {
  832               return value
  833                       .toString();
  834           }
  835           return quote(value.toString());
  836       }
  837   
  838       /**
  839        * Returns true if the other object is a JSONObject and its set of properties matches this object's properties.
  840        * <p/>
  841        * '
  842        */
  843       @Override
  844       public boolean equals(Object obj)
  845       {
  846           if (obj == null) return false;
  847   
  848           if (!(obj instanceof JSONObject)) return false;
  849   
  850           JSONObject other = (JSONObject) obj;
  851   
  852           return properties.equals(other.properties);
  853       }
  854   }

Home » tapestry-src-5.0.19 » org.apache.tapestry5.json » [javadoc | source]