001package com.hfg.javascript;
002
003
004
005import java.io.File;
006import java.io.IOException;
007import java.io.InputStream;
008import java.io.Reader;
009
010import com.hfg.util.FileUtil;
011import com.hfg.util.StringUtil;
012import com.hfg.util.io.StreamUtil;
013
014//------------------------------------------------------------------------------
015/**
016 JSON (serialized Javascript) utilities.
017
018 @author J. Alex Taylor, hairyfatguy.com
019 */
020//------------------------------------------------------------------------------
021// com.hfg Library
022//
023// This library is free software; you can redistribute it and/or
024// modify it under the terms of the GNU Lesser General Public
025// License as published by the Free Software Foundation; either
026// version 2.1 of the License, or (at your option) any later version.
027//
028// This library is distributed in the hope that it will be useful,
029// but WITHOUT ANY WARRANTY; without even the implied warranty of
030// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
031// Lesser General Public License for more details.
032//
033// You should have received a copy of the GNU Lesser General Public
034// License along with this library; if not, write to the Free Software
035// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
036//
037// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
038// jataylor@hairyfatguy.com
039//------------------------------------------------------------------------------
040
041public class JSONUtil
042{
043   //###########################################################################
044   // PUBLIC FUNCTIONS
045   //###########################################################################
046
047   //--------------------------------------------------------------------------
048   /**
049    * Converts a JSON file back into objects.
050    */
051   public static JsCollection toJsonObj(File inJsonFile)
052      throws IOException
053   {
054      if (! inJsonFile.exists())
055      {
056         throw new IOException("The specified JSON file " + StringUtil.singleQuote(inJsonFile.getPath()) + " doesn't exist!");
057      }
058      else if (! inJsonFile.canRead())
059      {
060         throw new IOException("No read permissions for the specified JSON file " + StringUtil.singleQuote(inJsonFile.getPath()) + "!");
061      }
062
063      return toJsonObj(FileUtil.read(inJsonFile));
064   }
065
066   //--------------------------------------------------------------------------
067   /**
068    * Converts a JSON Reader into objects.
069    */
070   public static JsCollection toJsonObj(Reader inJson)
071      throws IOException
072   {
073      return toJsonObj(StreamUtil.readerToString(inJson));
074   }
075
076   //--------------------------------------------------------------------------
077   /**
078    * Converts a JSON stream back into objects.
079    */
080   public static JsCollection toJsonObj(InputStream inJson)
081      throws IOException
082   {
083      return toJsonObj(StreamUtil.inputStreamToString(inJson));
084   }
085
086   //--------------------------------------------------------------------------
087   /**
088    * Converts a JSON string back into objects.
089    */
090   public static JsCollection toJsonObj(CharSequence inJSON)
091   {
092      JsCollection obj = null;
093
094      if (inJSON != null)
095      {
096         CharSequence json = stripLineComments(inJSON);
097
098         if (json.toString().trim().startsWith("["))
099         {
100            obj = new JsArray(json);
101         }
102         else if (json.toString().trim().startsWith("{"))
103         {
104            obj = new JsObjMap(json);
105         }
106         else
107         {
108            throw new RuntimeException("The JSON string must be enclosed in [] or {}.");
109         }
110      }
111
112      return obj;
113   }
114
115   //--------------------------------------------------------------------------
116   /**
117    * Escapes JSON string values according to <a href='http://www.ietf.org/rfc/rfc4627.txt'>RFC 4627</a>.
118    * @param inValue the String to be escaped
119    * @return  the escaped String value
120    */
121   public static String escapeString(String inValue)
122   {
123      String escapedValue = null;
124
125      if (inValue != null)
126      {
127         StringBuilder buffer = new StringBuilder();
128
129         for (int i = 0; i < inValue.length(); i++)
130         {
131            char theChar = inValue.charAt(i);
132            switch (theChar)
133            {
134               case '"':
135                  // Prepend quotes with an escape (if one isn't already present)
136//                  buffer.append((0 == buffer.length() || buffer.charAt(buffer.length() - 1) != '\\' ? "\\" : "") + "\"");
137                  buffer.append("\\\"");
138                  break;
139
140               case '\\':
141/*                  if (i < inValue.length() - 1
142                      && inValue.charAt(i + 1) == '\'')  // No need to escape single quotes in a double quoted string
143                  {
144                     // Do nothing.
145                  }
146                  else
147*/                  {
148                     buffer.append("\\\\");
149//                     buffer.append("\\");
150                  }
151                  break;
152
153               case '\r':
154                  buffer.append("\\r");
155                  break;
156
157               case '\n':
158                  buffer.append("\\n");
159                  break;
160
161               case '\t':
162                  buffer.append("\\t");
163                  break;
164
165               default:
166                  // A less common control character?
167                  if(theChar >='\u0000'
168                     && theChar <='\u001F')
169                  {
170                     String hexString = Integer.toHexString(theChar);
171                     buffer.append("\\u");
172                     // Prepend with the correct number of zeros
173                     for(int j = 0; j < 4 - hexString.length(); j++)
174                     {
175                        buffer.append('0');
176                     }
177                     buffer.append(hexString.toUpperCase());
178                  }
179                  else
180                  {
181                     // No escaping necessary
182                     buffer.append(theChar);
183                  }
184            }
185
186         }
187
188         escapedValue = buffer.toString();
189      }
190
191      return escapedValue;
192   }
193
194   //--------------------------------------------------------------------------
195   /**
196    * Un-escapes JSON string values according to <a href='http://www.ietf.org/rfc/rfc4627.txt'>RFC 4627</a>.
197    * @param inValue the String to be unescaped
198    * @return  the unescaped String value
199    */
200   public static String unescapeString(String inValue)
201   {
202      String unescapedString = null;
203
204      if (inValue != null)
205      {
206         unescapedString = inValue.replace("\\\"", "\"");
207         unescapedString = unescapedString.replace("\\\'", "'");
208         unescapedString = unescapedString.replace("\\r", "\r");
209         unescapedString = unescapedString.replace("\\n", "\n");
210         unescapedString = unescapedString.replace("\\t", "\t");
211         unescapedString = unescapedString.replace("\\\\", "\\");
212
213         //TODO: isocontrol characters
214      }
215
216      return unescapedString;
217   }
218
219   //--------------------------------------------------------------------------
220   /**
221    * This function strips any line comments from the JSON since they are not valid JSON syntax.
222    * @param inJSON the JSON string to be cleansed of line comments
223    * @return  the cleansed JSON string value
224    */
225   public static CharSequence stripLineComments(CharSequence inJSON)
226   {
227      String[] lines = StringUtil.lines(inJSON);
228
229      boolean commentsRemoved = false;
230
231      for (int lineIndex = 0; lineIndex < lines.length; lineIndex++)
232      {
233         String line = lines[lineIndex];
234
235         int startingIndex = 0;
236         int index;
237         boolean inQuotes = false;
238         while (startingIndex < line.length()
239                && (index = line.indexOf("//", startingIndex)) >= 0)
240         {
241            // We can't simply truncate from the '//' because they could be contained within quotes.
242
243            char prevChar = ' ';
244            for (int i = startingIndex; i < index; i++)
245            {
246               char theChar = line.charAt(i);
247
248               if (line.charAt(i) == '"'
249                     && prevChar != '\\') // Make sure it isn't an escaped quote
250               {
251                  inQuotes = !inQuotes;
252               }
253
254               prevChar = theChar;
255            }
256
257            if (! inQuotes)
258            {
259               lines[lineIndex] = line.substring(0, index);
260               commentsRemoved = true;
261               break;
262            }
263
264            startingIndex = index + 2;
265         }
266      }
267
268      return commentsRemoved ? StringUtil.join(lines, "\n") : inJSON;
269   }
270
271   //--------------------------------------------------------------------------
272   protected static Object convertStringValueToObject(String inStringValue)
273   {
274      Object valueObj;
275      try
276      {
277         if (inStringValue.equals("null")
278             || inStringValue.equals("undefined"))  // Coming from javascript, it may be 'undefined'
279         {
280            valueObj = null;
281         }
282         else if (inStringValue.equals("true"))
283         {
284            valueObj = Boolean.TRUE;
285         }
286         else if (inStringValue.equals("false"))
287         {
288            valueObj = Boolean.FALSE;
289         }
290         else if (inStringValue.indexOf(".") > 0)
291         {
292            valueObj = Double.parseDouble(inStringValue);
293         }
294         else
295         {
296            valueObj = Integer.parseInt(inStringValue);
297         }
298      }
299      catch (Exception e)
300      {
301         valueObj = inStringValue;
302      }
303
304      return valueObj;
305   }
306}