001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau; 014 015import static org.apache.juneau.internal.StringUtils.*; 016 017import java.io.*; 018import java.lang.reflect.*; 019import java.util.*; 020 021import org.apache.juneau.json.*; 022import org.apache.juneau.parser.*; 023import org.apache.juneau.serializer.*; 024import org.apache.juneau.utils.*; 025 026/** 027 * Java implementation of a JSON array. 028 * 029 * <p> 030 * An extension of {@link LinkedList}, so all methods available to in that class are also available to this class. 031 * 032 * <p> 033 * Note that the use of this class is optional. 034 * The serializers will accept any objects that implement the {@link Collection} interface. 035 * But this class provides some useful additional functionality when working with JSON models constructed from Java 036 * Collections Framework objects. 037 * For example, a constructor is provided for converting a JSON array string directly into a {@link List}. 038 * It also contains accessor methods for to avoid common typecasting when accessing elements in a list. 039 * 040 * <h5 class='section'>Example:</h5> 041 * <p class='bcode w800'> 042 * <jc>// Construct an empty List</jc> 043 * List l = <jk>new</jk> ObjectList(); 044 * 045 * <jc>// Construct a list of objects using various methods</jc> 046 * l = <jk>new</jk> ObjectList().append(<js>"foo"</js>).append(123).append(<jk>true</jk>); 047 * l = <jk>new</jk> ObjectList().append(<js>"foo"</js>, 123, <jk>true</jk>); <jc>// Equivalent</jc> 048 * l = <jk>new</jk> ObjectList(<js>"foo"</js>, 123, <jk>true</jk>); <jc>// Equivalent</jc> 049 * 050 * <jc>// Construct a list of integers from JSON</jc> 051 * l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>); 052 * 053 * <jc>// Construct a list of generic ObjectMap objects from JSON</jc> 054 * l = <jk>new</jk> ObjectList(<js>"[{foo:'bar'},{baz:'bing'}]"</js>); 055 * 056 * <jc>// Construct a list of integers from XML</jc> 057 * String xml = <js>"<array><number>1</number><number>2</number><number>3</number></array>"</js>; 058 * l = <jk>new</jk> ObjectList(xml, DataFormat.<jsf>XML</jsf>); 059 * l = (List)XmlParser.<jsf>DEFAULT</jsf>.parse(xml); <jc>// Equivalent</jc> 060 * l = (List)XmlParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, xml); <jc>// Equivalent</jc> 061 * l = XmlParser.<jsf>DEFAULT</jsf>.parse(List.<jk>class</jk>, xml); <jc>// Equivalent</jc> 062 * l = XmlParser.<jsf>DEFAULT</jsf>.parse(ObjectList.<jk>class</jk>, xml); <jc>// Equivalent</jc> 063 * 064 * <jc>// Construct JSON from ObjectList</jc> 065 * l = <jk>new</jk> ObjectList(<js>"[{foo:'bar'},{baz:'bing'}]"</js>); 066 * String json = l.toString(); <jc>// Produces "[{foo:'bar'},{baz:'bing'}]"</jc> 067 * json = l.toString(JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>); <jc>// Equivalent</jc> 068 * json = JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>.serialize(l); <jc>// Equivalent</jc> 069 * 070 * <jc>// Get one of the entries in the list as an Integer</jc> 071 * l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>); 072 * Integer i = l.getInt(1); 073 * i = l.get(Integer.<jk>class</jk>, 1); <jc>// Equivalent</jc> 074 * 075 * <jc>// Get one of the entries in the list as an Float</jc> 076 * l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>); 077 * Float f = l.getFloat(1); <jc>// Returns 2f </jc> 078 * f = l.get(Float.<jk>class</jk>, 1); <jc>// Equivalent</jc> 079 * 080 * <jc>// Same as above, except converted to a String</jc> 081 * l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>); 082 * String s = l.getString(1); <jc>// Returns "2" </jc> 083 * s = l.get(String.<jk>class</jk>, 1); <jc>// Equivalent</jc> 084 * 085 * <jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc> 086 * l = <jk>new</jk> ObjectList(<js>"[{name:'John Smith',age:45}]"</js>); 087 * Person p = l.get(Person.<jk>class</jk>, 0); 088 * 089 * <jc>// Iterate over a list of beans using the elements() method</jc> 090 * ObjectList ObjectList = <jk>new</jk> ObjectList(<js>"[{name:'John Smith',age:45}]"</js>); 091 * <jk>for</jk> (Person p : ObjectList.elements(Person.<jk>class</jk>) { 092 * <jc>// Do something with p</jc> 093 * } 094 * </p> 095 * 096 * <p> 097 * This class is not thread safe. 098 */ 099public class ObjectList extends LinkedList<Object> { 100 private static final long serialVersionUID = 1L; 101 102 transient BeanSession session = null; 103 private transient PojoRest pojoRest; 104 105 /** 106 * An empty read-only ObjectList. 107 */ 108 public static final ObjectList EMPTY_LIST = new ObjectList() { 109 private static final long serialVersionUID = 1L; 110 111 @Override /* List */ 112 public void add(int location, Object object) { 113 throw new UnsupportedOperationException(); 114 } 115 116 @Override /* List */ 117 public ListIterator<Object> listIterator(final int location) { 118 return Collections.emptyList().listIterator(location); 119 } 120 121 @Override /* List */ 122 public Object remove(int location) { 123 throw new UnsupportedOperationException(); 124 } 125 126 @Override /* List */ 127 public Object set(int location, Object object) { 128 throw new UnsupportedOperationException(); 129 } 130 131 @Override /* List */ 132 public List<Object> subList(int start, int end) { 133 return Collections.emptyList().subList(start, end); 134 } 135 }; 136 137 /** 138 * Static constructor from JSON string. 139 * 140 * @param s JSON initialization string. Can be <jk>null</jk>. 141 * @return A new {@link ObjectList} object, or <jk>null</jk> if the input is <jk>null</jk>. 142 * @throws ParseException Invalid JSON string. 143 */ 144 public static ObjectList parse(CharSequence s) throws ParseException { 145 return s == null ? null : new ObjectList(s); 146 } 147 148 /** 149 * Construct a JSON array directly from text using the specified parser. 150 * 151 * @param s The string being parsed. 152 * @param p The parser to use to parse the input. 153 * @throws ParseException Malformed input encountered. 154 */ 155 public ObjectList(CharSequence s, Parser p) throws ParseException { 156 this(p == null ? null : p.createBeanSession()); 157 if (p == null) 158 p = JsonParser.DEFAULT; 159 if (s != null) 160 p.parseIntoCollection(s, this, bs().object()); 161 } 162 163 /** 164 * Shortcut for <code><jk>new</jk> ObjectList(String,JsonParser.<jsf>DEFAULT</jsf>);</code> 165 * 166 * @param s The string being parsed. 167 * @throws ParseException Malformed input encountered. 168 */ 169 public ObjectList(CharSequence s) throws ParseException { 170 this(s, null); 171 } 172 173 /** 174 * Construct a JSON array directly from a reader using the specified parser. 175 * 176 * @param r 177 * The reader to read from. 178 * Will automatically be wrapped in a {@link BufferedReader} if it isn't already a BufferedReader. 179 * @param p The parser to use to parse the input. 180 * @throws ParseException Malformed input encountered. 181 * @throws IOException If a problem occurred trying to read from the reader. 182 */ 183 public ObjectList(Reader r, Parser p) throws ParseException, IOException { 184 this(p == null ? null : p.createBeanSession()); 185 parseReader(r, p); 186 } 187 188 /** 189 * Shortcut for <code><jk>new</jk> ObjectList(reader, JsonParser.<jsf>DEFAULT</jsf>)</code>. 190 * 191 * @param r 192 * The reader to read from. 193 * The reader will be wrapped in a {@link BufferedReader} if it isn't already. 194 * @throws ParseException Malformed input encountered. 195 * @throws IOException If a problem occurred trying to read from the reader. 196 */ 197 public ObjectList(Reader r) throws ParseException, IOException { 198 parseReader(r, JsonParser.DEFAULT); 199 } 200 201 private void parseReader(Reader r, Parser p) throws ParseException { 202 if (p == null) 203 p = JsonParser.DEFAULT; 204 p.parseIntoCollection(r, this, bs().object()); 205 } 206 207 /** 208 * Construct an empty JSON array (an empty {@link LinkedList}). 209 */ 210 public ObjectList() { 211 } 212 213 /** 214 * Construct an empty JSON array (an empty {@link LinkedList}) with the specified bean context. 215 * 216 * @param session The bean context to associate with this object list for creating beans. 217 */ 218 public ObjectList(BeanSession session) { 219 super(); 220 this.session = session; 221 } 222 223 /** 224 * Construct a JSON array and fill it with the specified objects. 225 * 226 * @param o A list of objects to add to this list. 227 */ 228 public ObjectList(Object... o) { 229 super(Arrays.asList(o)); 230 } 231 232 /** 233 * Construct a JSON array and fill it with the specified collection of objects. 234 * 235 * @param c A list of objects to add to this list. 236 */ 237 public ObjectList(Collection<?> c) { 238 super(c); 239 } 240 241 /** 242 * Override the default bean session used for converting POJOs. 243 * 244 * <p> 245 * Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases. 246 * 247 * <p> 248 * Useful if you're serializing/parsing beans with transforms defined. 249 * 250 * @param session The new bean session. 251 * @return This object (for method chaining). 252 */ 253 public ObjectList setBeanSession(BeanSession session) { 254 this.session = session; 255 return this; 256 } 257 258 /** 259 * Returns the {@link BeanSession} currently associated with this list. 260 * 261 * @return The {@link BeanSession} currently associated with this list. 262 */ 263 public BeanSession getBeanSession() { 264 return session; 265 } 266 267 /** 268 * Convenience method for adding multiple objects to this list. 269 * 270 * @param o The objects to add to the list. 271 * @return This object (for method chaining). 272 */ 273 public ObjectList append(Object...o) { 274 for (Object o2 : o) 275 add(o2); 276 return this; 277 } 278 279 /** 280 * Convenience method for appending another list to this list. 281 * 282 * @param l 283 * The list containing the elements to append to this list. 284 * <br>Can be <jk>null</jk>. 285 * @return This object (for method chaining). 286 */ 287 public ObjectList appendAll(ObjectList l) { 288 if (l != null) 289 addAll(l); 290 return this; 291 } 292 293 /** 294 * Convenience method for adding multiple objects to this list. 295 * 296 * <p> 297 * <jk>null</jk> and empty strings are skipped. 298 * 299 * @param o The objects to add to the list. 300 * @return This object (for method chaining). 301 */ 302 public ObjectList appendIfNotEmpty(String...o) { 303 for (String s : o) 304 if (isNotEmpty(s)) 305 add(s); 306 return this; 307 } 308 309 /** 310 * Convenience method for adding multiple objects to this list. 311 * 312 * <p> 313 * <jk>null</jk> values are skipped. 314 * 315 * @param o The objects to add to the list. 316 * @return This object (for method chaining). 317 */ 318 public ObjectList appendIfNotNull(Object...o) { 319 for (Object o2 : o) 320 if (o2 != null) 321 add(o2); 322 return this; 323 } 324 325 /** 326 * Get the entry at the specified index, converted to the specified type. 327 * 328 * <p> 329 * This is the preferred get method for simple types. 330 * 331 * <h5 class='section'>Examples:</h5> 332 * <p class='bcode w800'> 333 * ObjectList l = <jk>new</jk> ObjectList(<js>"..."</js>); 334 * 335 * <jc>// Value converted to a string.</jc> 336 * String s = l.get(1, String.<jk>class</jk>); 337 * 338 * <jc>// Value converted to a bean.</jc> 339 * MyBean b = l.get(2, MyBean.<jk>class</jk>); 340 * 341 * <jc>// Value converted to a bean array.</jc> 342 * MyBean[] ba = l.get(3, MyBean[].<jk>class</jk>); 343 * 344 * <jc>// Value converted to a linked-list of objects.</jc> 345 * List l1 = l.get(4, LinkedList.<jk>class</jk>); 346 * 347 * <jc>// Value converted to a map of object keys/values.</jc> 348 * Map m1 = l.get(5, TreeMap.<jk>class</jk>); 349 * </p> 350 * 351 * <p> 352 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 353 * 354 * @param index The index into this list. 355 * @param type The type of object to convert the entry to. 356 * @param <T> The type of object to convert the entry to. 357 * @return The converted entry. 358 */ 359 public <T> T get(int index, Class<T> type) { 360 return bs().convertToType(get(index), type); 361 } 362 363 /** 364 * Get the entry at the specified index, converted to the specified type. 365 * 366 * <p> 367 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 368 * 369 * <h5 class='section'>Examples:</h5> 370 * <p class='bcode w800'> 371 * ObjectList l = <jk>new</jk> ObjectList(<js>"..."</js>); 372 * 373 * <jc>// Value converted to a linked-list of strings.</jc> 374 * List<String> l1 = l.get(1, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 375 * 376 * <jc>// Value converted to a linked-list of beans.</jc> 377 * List<MyBean> l2 = l.get(2, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 378 * 379 * <jc>// Value converted to a linked-list of linked-lists of strings.</jc> 380 * List<List<String>> l3 = l.get(3, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 381 * 382 * <jc>// Value converted to a map of string keys/values.</jc> 383 * Map<String,String> m1 = l.get(4, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 384 * 385 * <jc>// Value converted to a map containing string keys and values of lists containing beans.</jc> 386 * Map<String,List<MyBean>> m2 = l.get(5, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 387 * </p> 388 * 389 * <p> 390 * <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. 391 * 392 * <p> 393 * <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value types. 394 * 395 * <p> 396 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 397 * 398 * <p> 399 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 400 * 401 * @param index The index into this list. 402 * @param type The type of object to convert the entry to. 403 * @param args The type arguments of the type to convert the entry to. 404 * @param <T> The type of object to convert the entry to. 405 * @return The converted entry. 406 */ 407 public <T> T get(int index, Type type, Type...args) { 408 return bs().convertToType(get(index), type, args); 409 } 410 411 /** 412 * Shortcut for calling <code>get(index, String.<jk>class</jk>)</code>. 413 * 414 * @param index The index. 415 * @return The converted value. 416 */ 417 public String getString(int index) { 418 return get(index, String.class); 419 } 420 421 /** 422 * Shortcut for calling <code>get(index, Integer.<jk>class</jk>)</code>. 423 * 424 * @param index The index. 425 * @return The converted value. 426 * @throws InvalidDataConversionException If value cannot be converted. 427 */ 428 public Integer getInt(int index) { 429 return get(index, Integer.class); 430 } 431 432 /** 433 * Shortcut for calling <code>get(index, Boolean.<jk>class</jk>)</code>. 434 * 435 * @param index The index. 436 * @return The converted value. 437 * @throws InvalidDataConversionException If value cannot be converted. 438 */ 439 public Boolean getBoolean(int index) { 440 return get(index, Boolean.class); 441 } 442 443 /** 444 * Shortcut for calling <code>get(index, Long.<jk>class</jk>)</code>. 445 * 446 * @param index The index. 447 * @return The converted value. 448 * @throws InvalidDataConversionException If value cannot be converted. 449 */ 450 public Long getLong(int index) { 451 return get(index, Long.class); 452 } 453 454 /** 455 * Shortcut for calling <code>get(index, Map.<jk>class</jk>)</code>. 456 * 457 * @param index The index. 458 * @return The converted value. 459 * @throws InvalidDataConversionException If value cannot be converted. 460 */ 461 public Map<?,?> getMap(int index) { 462 return get(index, Map.class); 463 } 464 465 /** 466 * Same as {@link #getMap(int)} except converts the keys and values to the specified types. 467 * 468 * @param index The index. 469 * @param keyType The key type class. 470 * @param valType The value type class. 471 * @return The converted value. 472 * @throws InvalidDataConversionException If value cannot be converted. 473 */ 474 public <K,V> Map<K,V> getMap(int index, Class<K> keyType, Class<V> valType) { 475 return bs().convertToType(get(index), Map.class, keyType, valType); 476 } 477 478 /** 479 * Shortcut for calling <code>get(index, List.<jk>class</jk>)</code>. 480 * 481 * @param index The index. 482 * @return The converted value. 483 * @throws InvalidDataConversionException If value cannot be converted. 484 */ 485 public List<?> getList(int index) { 486 return get(index, List.class); 487 } 488 489 /** 490 * Same as {@link #getList(int)} except converts the elements to the specified types. 491 * 492 * @param index The index. 493 * @param elementType The element type class. 494 * @return The converted value. 495 * @throws InvalidDataConversionException If value cannot be converted. 496 */ 497 public <E> List<E> getList(int index, Class<E> elementType) { 498 return bs().convertToType(get(index), List.class, elementType); 499 } 500 501 /** 502 * Shortcut for calling <code>get(index, ObjectMap.<jk>class</jk>)</code>. 503 * 504 * @param index The index. 505 * @return The converted value. 506 * @throws InvalidDataConversionException If value cannot be converted. 507 */ 508 public ObjectMap getObjectMap(int index) { 509 return get(index, ObjectMap.class); 510 } 511 512 /** 513 * Shortcut for calling <code>get(index, ObjectList.<jk>class</jk>)</code>. 514 * 515 * @param index The index. 516 * @return The converted value. 517 * @throws InvalidDataConversionException If value cannot be converted. 518 */ 519 public ObjectList getObjectList(int index) { 520 return get(index, ObjectList.class); 521 } 522 523 /** 524 * Same as {@link #get(int,Class) get(int,Class)}, but the key is a slash-delimited path used to traverse entries in 525 * this POJO. 526 * 527 * <p> 528 * For example, the following code is equivalent: 529 * </p> 530 * <p class='bcode w800'> 531 * ObjectMap m = getObjectMap(); 532 * 533 * <jc>// Long way</jc> 534 * <jk>long</jk> l = m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>).getLong(<js>"baz"</js>); 535 * 536 * <jc>// Using this method</jc> 537 * <jk>long</jk> l = m.getAt(<js>"foo/bar/0/baz"</js>, <jk>long</jk>.<jk>class</jk>); 538 * </p> 539 * 540 * <p> 541 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 542 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 543 * 544 * @param path The path to the entry. 545 * @param type The class type. 546 * 547 * @param <T> The class type. 548 * @return The value, or <jk>null</jk> if the entry doesn't exist. 549 */ 550 public <T> T getAt(String path, Class<T> type) { 551 return getPojoRest().get(path, type); 552 } 553 554 /** 555 * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections. 556 * 557 * @param path The path to the entry. 558 * @param type The class type. 559 * @param args The class parameter types. 560 * 561 * @param <T> The class type. 562 * @return The value, or <jk>null</jk> if the entry doesn't exist. 563 */ 564 public <T> T getAt(String path, Type type, Type...args) { 565 return getPojoRest().get(path, type, args); 566 } 567 568 /** 569 * Same as {@link #set(int,Object) set(int,Object)}, but the key is a slash-delimited path used to traverse entries 570 * in this POJO. 571 * 572 * <p> 573 * For example, the following code is equivalent: 574 * </p> 575 * <p class='bcode w800'> 576 * ObjectMap m = getObjectMap(); 577 * 578 * <jc>// Long way</jc> 579 * m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>).put(<js>"baz"</js>, 123); 580 * 581 * <jc>// Using this method</jc> 582 * m.putAt(<js>"foo/bar/0/baz"</js>, 123); 583 * </p> 584 * 585 * <p> 586 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 587 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 588 * 589 * @param path The path to the entry. 590 * @param o The new value. 591 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 592 */ 593 public Object putAt(String path, Object o) { 594 return getPojoRest().put(path, o); 595 } 596 597 /** 598 * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays. 599 * 600 * <p> 601 * For example, the following code is equivalent: 602 * </p> 603 * <p class='bcode w800'> 604 * ObjectMap m = getObjectMap(); 605 * 606 * <jc>// Long way</jc> 607 * m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).append(123); 608 * 609 * <jc>// Using this method</jc> 610 * m.postAt(<js>"foo/bar"</js>, 123); 611 * </p> 612 * 613 * <p> 614 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 615 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 616 * 617 * @param path The path to the entry. 618 * @param o The new value. 619 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 620 */ 621 public Object postAt(String path, Object o) { 622 return getPojoRest().post(path, o); 623 } 624 625 /** 626 * Similar to {@link #remove(int) remove(int)},but the key is a slash-delimited path used to traverse entries in 627 * this POJO. 628 * 629 * <p> 630 * For example, the following code is equivalent: 631 * </p> 632 * <p class='bcode w800'> 633 * ObjectMap m = getObjectMap(); 634 * 635 * <jc>// Long way</jc> 636 * m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(1).remove(<js>"baz"</js>); 637 * 638 * <jc>// Using this method</jc> 639 * m.deleteAt(<js>"foo/bar/0/baz"</js>); 640 * </p> 641 * 642 * <p> 643 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 644 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 645 * 646 * @param path The path to the entry. 647 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 648 */ 649 public Object deleteAt(String path) { 650 return getPojoRest().delete(path); 651 } 652 653 /** 654 * Creates an {@link Iterable} with elements of the specified child type. 655 * 656 * <p> 657 * Attempts to convert the child objects to the correct type if they aren't already the correct type. 658 * 659 * <p> 660 * The <c>next()</c> method on the returned iterator may throw a {@link InvalidDataConversionException} if 661 * the next element cannot be converted to the specified type. 662 * 663 * <p> 664 * See {@link BeanSession#convertToType(Object, ClassMeta)} for a description of valid conversions. 665 * 666 * <h5 class='section'>Example:</h5> 667 * <p class='bcode w800'> 668 * <jc>// Iterate over a list of ObjectMaps.</jc> 669 * ObjectList l = <jk>new</jk> ObjectList(<js>"[{foo:'bar'},{baz:123}]"</js>); 670 * for (ObjectMap m : l.elements(ObjectMap.<jk>class</jk>)) { 671 * <jc>// Do something with m.</jc> 672 * } 673 * 674 * <jc>// Iterate over a list of ints.</jc> 675 * ObjectList l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>); 676 * for (Integer i : l.elements(Integer.<jk>class</jk>)) { 677 * <jc>// Do something with i.</jc> 678 * } 679 * 680 * <jc>// Iterate over a list of beans.</jc> 681 * <jc>// Automatically converts to beans.</jc> 682 * ObjectList l = <jk>new</jk> ObjectList(<js>"[{name:'John Smith',age:45}]"</js>); 683 * for (Person p : l.elements(Person.<jk>class</jk>)) { 684 * <jc>// Do something with p.</jc> 685 * } 686 * </p> 687 * 688 * @param <E> The child object type. 689 * @param childType The child object type. 690 * @return A new <c>Iterable</c> object over this list. 691 */ 692 public <E> Iterable<E> elements(final Class<E> childType) { 693 final Iterator<?> i = iterator(); 694 return new Iterable<E>() { 695 696 @Override /* Iterable */ 697 public Iterator<E> iterator() { 698 return new Iterator<E>() { 699 700 @Override /* Iterator */ 701 public boolean hasNext() { 702 return i.hasNext(); 703 } 704 705 @Override /* Iterator */ 706 public E next() { 707 return bs().convertToType(i.next(), childType); 708 } 709 710 @Override /* Iterator */ 711 public void remove() { 712 i.remove(); 713 } 714 715 }; 716 } 717 }; 718 } 719 720 /** 721 * Returns the {@link ClassMeta} of the class of the object at the specified index. 722 * 723 * @param index An index into this list, zero-based. 724 * @return The data type of the object at the specified index, or <jk>null</jk> if the value is null. 725 */ 726 public ClassMeta<?> getClassMeta(int index) { 727 return bs().getClassMetaForObject(get(index)); 728 } 729 730 private PojoRest getPojoRest() { 731 if (pojoRest == null) 732 pojoRest = new PojoRest(this); 733 return pojoRest; 734 } 735 736 /** 737 * Serialize this array to a string using the specified serializer. 738 * 739 * @param serializer The serializer to use to convert this object to a string. 740 * @return This object as a serialized string. 741 * @throws SerializeException If a problem occurred trying to convert the output. 742 */ 743 public String toString(WriterSerializer serializer) throws SerializeException { 744 return serializer.serialize(this); 745 } 746 747 /** 748 * Returns <jk>true</jk> if this list is unmodifiable. 749 * 750 * @return <jk>true</jk> if this list is unmodifiable. 751 */ 752 public boolean isUnmodifiable() { 753 return false; 754 } 755 756 /** 757 * Returns a modifiable copy of this list if it's unmodifiable. 758 * 759 * @return A modifiable copy of this list if it's unmodifiable, or this list if it is already modifiable. 760 */ 761 public ObjectList modifiable() { 762 if (isUnmodifiable()) 763 return new ObjectList(this); 764 return this; 765 } 766 767 /** 768 * Returns an unmodifiable copy of this list if it's modifiable. 769 * 770 * @return An unmodifiable copy of this list if it's modifiable, or this list if it is already unmodifiable. 771 */ 772 public ObjectList unmodifiable() { 773 if (this instanceof UnmodifiableObjectList) 774 return this; 775 return new UnmodifiableObjectList(this); 776 } 777 778 /** 779 * Serialize this array to JSON using the {@link JsonSerializer#DEFAULT} serializer. 780 */ 781 @Override /* Object */ 782 public String toString() { 783 try { 784 return this.toString(SimpleJsonSerializer.DEFAULT); 785 } catch (SerializeException e) { 786 return e.getLocalizedMessage(); 787 } 788 } 789 790 /** 791 * Convenience method for serializing this ObjectList to the specified Writer using the JsonSerializer.DEFAULT 792 * serializer. 793 * 794 * @param w The writer to send the serialized contents of this object. 795 * @throws IOException If a problem occurred trying to write to the writer. 796 * @throws SerializeException If a problem occurred trying to convert the output. 797 */ 798 public void serializeTo(Writer w) throws IOException, SerializeException { 799 JsonSerializer.DEFAULT.serialize(this); 800 } 801 802 /** 803 * Converts this object into the specified class type. 804 * 805 * <p> 806 * TODO - The current implementation is very inefficient. 807 * 808 * @param cm The class type to convert this object to. 809 * @return A converted object. 810 */ 811 public Object cast(ClassMeta<?> cm) { 812 try { 813 return JsonParser.DEFAULT.parse(SimpleJsonSerializer.DEFAULT.serialize(this), cm); 814 } catch (ParseException | SerializeException e) { 815 throw new RuntimeException(e); 816 } 817 } 818 819 private static final class UnmodifiableObjectList extends ObjectList { 820 private static final long serialVersionUID = 1L; 821 822 UnmodifiableObjectList(ObjectList contents) { 823 super(); 824 if (contents != null) { 825 for (Object e : this) { 826 super.add(e); 827 } 828 } 829 } 830 831 @Override /* List */ 832 public void add(int location, Object object) { 833 throw new UnsupportedOperationException("ObjectList is read-only."); 834 } 835 836 @Override /* List */ 837 public Object remove(int location) { 838 throw new UnsupportedOperationException("ObjectList is read-only."); 839 } 840 841 @Override /* List */ 842 public Object set(int location, Object object) { 843 throw new UnsupportedOperationException("ObjectList is read-only."); 844 } 845 846 @Override 847 public final boolean isUnmodifiable() { 848 return true; 849 } 850 } 851 852 BeanSession bs() { 853 if (session == null) 854 session = BeanContext.DEFAULT.createBeanSession(); 855 return session; 856 } 857}