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}