001package com.hfg.javascript;
002
003import java.io.*;
004import java.util.*;
005import java.util.regex.Matcher;
006import java.util.regex.Pattern;
007
008import com.hfg.util.StringUtil;
009import com.hfg.xml.XMLException;
010
011//------------------------------------------------------------------------------
012/**
013 A Javascript array container useful for creating JSON.
014 <div>
015 JSON RFC: <a href='http://www.ietf.org/rfc/rfc4627.txt'>http://www.ietf.org/rfc/rfc4627.txt</a>
016 </div>
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 JsArray extends ArrayList<Object> implements JsCollection
042{
043   private static final Pattern UNQUOTED_VALUE_PATTERN = Pattern.compile("(.+?)\\s*[,}\\]]");
044
045   //--------------------------------------------------------------------------
046   public JsArray()
047   {
048      super();
049   }
050
051   //--------------------------------------------------------------------------
052   public JsArray(int inInitialCapacity)
053   {
054      super(inInitialCapacity);
055   }
056
057   //--------------------------------------------------------------------------
058   public JsArray(Collection inCollection)
059   {
060      super(inCollection);
061   }
062
063   //--------------------------------------------------------------------------
064   public JsArray(Object[] inArray)
065   {
066      this();
067      if (inArray != null)
068      {
069         for (Object obj : inArray)
070         {
071            add(obj);
072         }
073      }
074   }
075
076   //--------------------------------------------------------------------------
077   public JsArray(int[] inArray)
078   {
079      this();
080      if (inArray != null)
081      {
082         for (Object obj : inArray)
083         {
084            add(obj);
085         }
086      }
087   }
088
089   //--------------------------------------------------------------------------
090   public JsArray(long[] inArray)
091   {
092      this();
093      if (inArray != null)
094      {
095         for (Object obj : inArray)
096         {
097            add(obj);
098         }
099      }
100   }
101
102   //--------------------------------------------------------------------------
103   public JsArray(float[] inArray)
104   {
105      this();
106      if (inArray != null)
107      {
108         for (Object obj : inArray)
109         {
110            add(obj);
111         }
112      }
113   }
114
115   //--------------------------------------------------------------------------
116   public JsArray(double[] inArray)
117   {
118      this();
119      if (inArray != null)
120      {
121         for (Object obj : inArray)
122         {
123            add(obj);
124         }
125      }
126   }
127
128   //--------------------------------------------------------------------------
129   public JsArray(CharSequence inJSONString)
130   {
131      if (StringUtil.isSet(inJSONString))
132      {
133         parse(inJSONString);
134      }
135   }
136
137   //###########################################################################
138   // PUBLIC METHODS
139   //###########################################################################
140
141   //--------------------------------------------------------------------------
142   @Override
143   public String toString()
144   {
145      return toJSON();
146   }
147
148   //--------------------------------------------------------------------------
149   public String toJSON()
150   {
151      ByteArrayOutputStream outStream;
152      try
153      {
154         outStream = new ByteArrayOutputStream(2048);
155         toJSON(outStream);
156         outStream.close();
157      }
158      catch (Exception e)
159      {
160         throw new XMLException(e);
161      }
162
163      return outStream.toString();
164   }
165
166   //---------------------------------------------------------------------------
167   public void toJSON(OutputStream inStream)
168   {
169      PrintWriter writer = new PrintWriter(inStream);
170
171      toJSON(writer);
172
173      writer.flush();
174   }
175
176   //--------------------------------------------------------------------------
177   public void toJSON(Writer inWriter)
178   {
179      try
180      {
181         inWriter.write("[ ");
182
183         int i = 0;
184         for (Object value : this)
185         {
186            if (i > 0)
187            {
188               inWriter.write(", ");
189            }
190
191            inWriter.write(composeValueJSON(value));
192
193            i++;
194         }
195
196         inWriter.write(" ]");
197      }
198      catch (IOException e)
199      {
200         throw new RuntimeException(e);
201      }
202   }
203
204   //--------------------------------------------------------------------------
205   public String toJavascript()
206   {
207      ByteArrayOutputStream outStream;
208      try
209      {
210         outStream = new ByteArrayOutputStream(2048);
211         toJavascript(outStream);
212         outStream.close();
213      }
214      catch (Exception e)
215      {
216         throw new XMLException(e);
217      }
218
219      return outStream.toString();
220   }
221
222   //---------------------------------------------------------------------------
223   public void toJavascript(OutputStream inStream)
224   {
225      PrintWriter writer = new PrintWriter(inStream);
226
227      toJavascript(writer);
228
229      writer.flush();
230   }
231
232   //--------------------------------------------------------------------------
233   public void toJavascript(Writer inWriter)
234   {
235      try
236      {
237         inWriter.write("[ ");
238
239         int i = 0;
240         for (Object value : this)
241         {
242            if (i > 0)
243            {
244               inWriter.write(", ");
245            }
246
247            inWriter.write(composeValueJavascript(value));
248
249            i++;
250         }
251
252         inWriter.write(" ]");
253      }
254      catch (IOException e)
255      {
256         throw new RuntimeException(e);
257      }
258   }
259
260   //--------------------------------------------------------------------------
261   public String getString(int inIndex)
262   {
263      Object value = get(inIndex);
264      return (value != null ? value.toString() : null);
265   }
266
267   //###########################################################################
268   // PRIVATE METHODS
269   //###########################################################################
270
271   //--------------------------------------------------------------------------
272   private String composeValueJSON(Object inValue)
273   {
274      String value = "null";
275
276      if (inValue != null)
277      {
278         if (inValue instanceof Number
279             || inValue instanceof Boolean)
280         {
281            value = inValue.toString();
282         }
283         else if (inValue instanceof JsArray)
284         {
285            value = ((JsArray) inValue).toJSON();
286         }
287         else if (inValue instanceof JsObjMap)
288         {
289            value = ((JsObjMap) inValue).toJSON();
290         }
291         else
292         {
293            value = "\"" + JSONUtil.escapeString(inValue.toString()) + "\"";
294         }
295      }
296
297      return value;
298   }
299
300   //--------------------------------------------------------------------------
301   private String composeValueJavascript(Object inValue)
302   {
303      String value = "null";
304
305      if (inValue != null)
306      {
307         if (inValue instanceof Number
308             || inValue instanceof Boolean)
309         {
310            value = inValue.toString();
311         }
312         else if (inValue instanceof JsArray)
313         {
314            value = ((JsArray) inValue).toJavascript();
315         }
316         else if (inValue instanceof JsObjMap)
317         {
318            value = ((JsObjMap) inValue).toJavascript();
319         }
320         else
321         {
322            value = "\"" + JSONUtil.escapeString(inValue.toString()) + "\"";
323         }
324      }
325
326      return value;
327   }
328
329   //--------------------------------------------------------------------------
330   private void parse(CharSequence inString)
331   {
332      String str = inString.toString().trim();
333      if (str.charAt(0) != '['
334          || str.charAt(str.length() - 1) != ']')
335      {
336         throw new RuntimeException("JSON array string must be enclosed with []!");
337      }
338
339      int index = 1;
340
341      while (index < str.length() - 1)
342      {
343         char theChar = str.charAt(index);
344         if (Character.isWhitespace(theChar)
345             || theChar == ',')
346         {
347            index++;
348         }
349         else if (theChar == '[')  // Array within the array
350         {
351            // Find the ending bracket
352            int depth = 1;
353            char quote = ' ';
354            boolean inEscape = false;
355            boolean inValue = false;
356            int end = index + 1;
357            while (end < str.length() -1
358                   && (depth != 1
359                       || inValue
360                       || str.charAt(end) != ']'))
361            {
362               char theEndChar = str.charAt(end);
363               if (theEndChar == '\\'
364                   && inValue)
365               {
366                  inEscape = ! inEscape;
367               }
368               else
369               {
370                  if (theEndChar == '\''
371                      && (str.charAt(end - 1) != '\\'
372                          || !inEscape))
373                  {
374                     if (!inValue)
375                     {
376                        inValue = true;
377                        quote = '\'';
378                     }
379                     else if (quote == '\'')
380                     {
381                        inValue = false;
382                     }
383                  }
384                  else if (theEndChar == '"'
385                           && (str.charAt(end - 1) != '\\'
386                               || !inEscape))
387                  {
388                     if (!inValue)
389                     {
390                        inValue = true;
391                        quote = '"';
392                     }
393                     else if (quote == '"')
394                     {
395                        inValue = false;
396                     }
397                  }
398                  else if (!inValue
399                           && theEndChar == '[')
400                  {
401                     depth++;
402                  }
403                  else if (!inValue
404                           && theEndChar == ']')
405                  {
406                     depth--;
407                  }
408
409                  if (inEscape)
410                  {
411                     inEscape = false;
412                  }
413               }
414
415               end++;
416            }
417
418            JsArray value = new JsArray(str.substring(index, end + 1));
419            add(value);
420            index = end + 1;
421         }
422         else if (theChar == '{')   // Map within the array
423         {
424            // Find the ending brace
425            int depth = 1;
426            char quote = ' ';
427            boolean inValue = false;
428            int escapeIdx = -10;
429            int end = index + 1;
430            while (end < str.length() -1
431                   && (depth != 1
432                       || inValue
433                       || str.charAt(end) != '}'))
434            {
435               char theEndChar = str.charAt(end);
436               if (theEndChar == '\\'
437                   && inValue
438                   && escapeIdx < end - 1)
439               {
440                  escapeIdx = end;
441               }
442
443               if (theEndChar == '\''
444                        && (str.charAt(end -1) != '\\'
445                            || escapeIdx < end - 1))
446               {
447                  if (! inValue)
448                  {
449                     inValue = true;
450                     quote = '\'';
451                  }
452                  else if (quote == '\'')
453                  {
454                     inValue = false;
455                  }
456               }
457               else if (theEndChar == '"'
458                        && (str.charAt(end -1) != '\\'
459                            || escapeIdx < end - 1))
460               {
461                  if (! inValue)
462                  {
463                     inValue = true;
464                     quote = '"';
465                  }
466                  else if (quote == '"')
467                  {
468                     inValue = false;
469                  }
470               }
471               else if (! inValue
472                        && theEndChar == '{')
473               {
474                  depth++;
475               }
476               else if (! inValue
477                        && theEndChar == '}')
478               {
479                  depth--;
480               }
481
482               end++;
483            }
484
485//            JsObjMap value = new JsObjMap(StringUtil.replaceAll(str.substring(index, end + 1), "\\", "\\\\"));
486            JsObjMap value = new JsObjMap(str.substring(index, end + 1));
487            add(value);
488            index = end + 1;
489         }
490         else
491         {
492            if (theChar == '"')
493            {
494               boolean inEscape = false;
495               int valueStartIndex = index;
496               StringBuilder valueBuffer = new StringBuilder();
497               // Continue until we find the next unescaped quote.
498               while ((index++) < str.length() - 1
499                      && ((theChar = str.charAt(index)) != '"')  || inEscape)
500               {
501                  if (theChar == '\\')
502                  {
503                     inEscape = ! inEscape;
504                  }
505                  else if (inEscape)
506                  {
507                     inEscape = false;
508                  }
509
510                  if (! inEscape)
511                  {
512                     valueBuffer.append(theChar);
513                  }
514               }
515
516               if (index == str.length() - 1)
517               {
518                  throw new RuntimeException("Problem parsing value @ position " + valueStartIndex + " in JSON string!");
519               }
520
521               index++; // consume the trailing quote
522               add(JSONUtil.unescapeString(valueBuffer.toString()));
523            }
524            else
525            {
526               Matcher m = UNQUOTED_VALUE_PATTERN.matcher(str);
527               if (m.find(index))
528               {
529                  String stringValue = JSONUtil.unescapeString(m.group(1));
530                  index = m.end() - 1;
531
532                  add(JSONUtil.convertStringValueToObject(stringValue));
533               }
534            }
535         }
536      }
537   }
538
539}