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, bs2().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, bs2().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(); 230 Collections.addAll(this, o); 231 } 232 233 /** 234 * Construct a JSON array and fill it with the specified collection of objects. 235 * 236 * @param c A list of objects to add to this list. 237 */ 238 public ObjectList(Collection<?> c) { 239 super(c); 240 } 241 242 /** 243 * Override the default bean session used for converting POJOs. 244 * 245 * <p> 246 * Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases. 247 * 248 * <p> 249 * Useful if you're serializing/parsing beans with transforms defined. 250 * 251 * @param session The new bean session. 252 * @return This object (for method chaining). 253 */ 254 public ObjectList setBeanSession(BeanSession session) { 255 this.session = session; 256 return this; 257 } 258 259 /** 260 * Returns the {@link BeanSession} currently associated with this list. 261 * 262 * @return The {@link BeanSession} currently associated with this list. 263 */ 264 public BeanSession getBeanSession() { 265 return session; 266 } 267 268 /** 269 * Convenience method for adding multiple objects to this list. 270 * 271 * @param o The objects to add to the list. 272 * @return This object (for method chaining). 273 */ 274 public ObjectList append(Object...o) { 275 for (Object o2 : o) 276 add(o2); 277 return this; 278 } 279 280 /** 281 * Convenience method for appending another list to this list. 282 * 283 * @param l 284 * The list containing the elements to append to this list. 285 * <br>Can be <jk>null</jk>. 286 * @return This object (for method chaining). 287 */ 288 public ObjectList appendAll(ObjectList l) { 289 if (l != null) 290 addAll(l); 291 return this; 292 } 293 294 /** 295 * Convenience method for adding multiple objects to this list. 296 * 297 * <p> 298 * <jk>null</jk> and empty strings are skipped. 299 * 300 * @param o The objects to add to the list. 301 * @return This object (for method chaining). 302 */ 303 public ObjectList appendIfNotEmpty(String...o) { 304 for (String s : o) 305 if (isNotEmpty(s)) 306 add(s); 307 return this; 308 } 309 310 /** 311 * Convenience method for adding multiple objects to this list. 312 * 313 * <p> 314 * <jk>null</jk> values are skipped. 315 * 316 * @param o The objects to add to the list. 317 * @return This object (for method chaining). 318 */ 319 public ObjectList appendIfNotNull(Object...o) { 320 for (Object o2 : o) 321 if (o2 != null) 322 add(o2); 323 return this; 324 } 325 326 /** 327 * Get the entry at the specified index, converted to the specified type. 328 * 329 * <p> 330 * This is the preferred get method for simple types. 331 * 332 * <h5 class='section'>Examples:</h5> 333 * <p class='bcode w800'> 334 * ObjectList l = <jk>new</jk> ObjectList(<js>"..."</js>); 335 * 336 * <jc>// Value converted to a string.</jc> 337 * String s = l.get(1, String.<jk>class</jk>); 338 * 339 * <jc>// Value converted to a bean.</jc> 340 * MyBean b = l.get(2, MyBean.<jk>class</jk>); 341 * 342 * <jc>// Value converted to a bean array.</jc> 343 * MyBean[] ba = l.get(3, MyBean[].<jk>class</jk>); 344 * 345 * <jc>// Value converted to a linked-list of objects.</jc> 346 * List l1 = l.get(4, LinkedList.<jk>class</jk>); 347 * 348 * <jc>// Value converted to a map of object keys/values.</jc> 349 * Map m1 = l.get(5, TreeMap.<jk>class</jk>); 350 * </p> 351 * 352 * <p> 353 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 354 * 355 * @param index The index into this list. 356 * @param type The type of object to convert the entry to. 357 * @param <T> The type of object to convert the entry to. 358 * @return The converted entry. 359 */ 360 public <T> T get(int index, Class<T> type) { 361 return bs2().convertToType(get(index), type); 362 } 363 364 /** 365 * Get the entry at the specified index, converted to the specified type. 366 * 367 * <p> 368 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 369 * 370 * <h5 class='section'>Examples:</h5> 371 * <p class='bcode w800'> 372 * ObjectList l = <jk>new</jk> ObjectList(<js>"..."</js>); 373 * 374 * <jc>// Value converted to a linked-list of strings.</jc> 375 * List<String> l1 = l.get(1, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 376 * 377 * <jc>// Value converted to a linked-list of beans.</jc> 378 * List<MyBean> l2 = l.get(2, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 379 * 380 * <jc>// Value converted to a linked-list of linked-lists of strings.</jc> 381 * List<List<String>> l3 = l.get(3, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 382 * 383 * <jc>// Value converted to a map of string keys/values.</jc> 384 * Map<String,String> m1 = l.get(4, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 385 * 386 * <jc>// Value converted to a map containing string keys and values of lists containing beans.</jc> 387 * Map<String,List<MyBean>> m2 = l.get(5, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 388 * </p> 389 * 390 * <p> 391 * <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. 392 * 393 * <p> 394 * <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value types. 395 * 396 * <p> 397 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 398 * 399 * <p> 400 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 401 * 402 * @param index The index into this list. 403 * @param type The type of object to convert the entry to. 404 * @param args The type arguments of the type to convert the entry to. 405 * @param <T> The type of object to convert the entry to. 406 * @return The converted entry. 407 */ 408 public <T> T get(int index, Type type, Type...args) { 409 return bs2().convertToType(get(index), type, args); 410 } 411 412 /** 413 * Shortcut for calling <code>get(index, String.<jk>class</jk>)</code>. 414 * 415 * @param index The index. 416 * @return The converted value. 417 */ 418 public String getString(int index) { 419 return get(index, String.class); 420 } 421 422 /** 423 * Shortcut for calling <code>get(index, Integer.<jk>class</jk>)</code>. 424 * 425 * @param index The index. 426 * @return The converted value. 427 * @throws InvalidDataConversionException If value cannot be converted. 428 */ 429 public Integer getInt(int index) { 430 return get(index, Integer.class); 431 } 432 433 /** 434 * Shortcut for calling <code>get(index, Boolean.<jk>class</jk>)</code>. 435 * 436 * @param index The index. 437 * @return The converted value. 438 * @throws InvalidDataConversionException If value cannot be converted. 439 */ 440 public Boolean getBoolean(int index) { 441 return get(index, Boolean.class); 442 } 443 444 /** 445 * Shortcut for calling <code>get(index, Long.<jk>class</jk>)</code>. 446 * 447 * @param index The index. 448 * @return The converted value. 449 * @throws InvalidDataConversionException If value cannot be converted. 450 */ 451 public Long getLong(int index) { 452 return get(index, Long.class); 453 } 454 455 /** 456 * Shortcut for calling <code>get(index, Map.<jk>class</jk>)</code>. 457 * 458 * @param index The index. 459 * @return The converted value. 460 * @throws InvalidDataConversionException If value cannot be converted. 461 */ 462 public Map<?,?> getMap(int index) { 463 return get(index, Map.class); 464 } 465 466 /** 467 * Same as {@link #getMap(int)} except converts the keys and values to the specified types. 468 * 469 * @param index The index. 470 * @param keyType The key type class. 471 * @param valType The value type class. 472 * @return The converted value. 473 * @throws InvalidDataConversionException If value cannot be converted. 474 */ 475 public <K,V> Map<K,V> getMap(int index, Class<K> keyType, Class<V> valType) { 476 return bs2().convertToType(get(index), Map.class, keyType, valType); 477 } 478 479 /** 480 * Shortcut for calling <code>get(index, List.<jk>class</jk>)</code>. 481 * 482 * @param index The index. 483 * @return The converted value. 484 * @throws InvalidDataConversionException If value cannot be converted. 485 */ 486 public List<?> getList(int index) { 487 return get(index, List.class); 488 } 489 490 /** 491 * Same as {@link #getList(int)} except converts the elements to the specified types. 492 * 493 * @param index The index. 494 * @param elementType The element type class. 495 * @return The converted value. 496 * @throws InvalidDataConversionException If value cannot be converted. 497 */ 498 public <E> List<E> getList(int index, Class<E> elementType) { 499 return bs2().convertToType(get(index), List.class, elementType); 500 } 501 502 /** 503 * Shortcut for calling <code>get(index, ObjectMap.<jk>class</jk>)</code>. 504 * 505 * @param index The index. 506 * @return The converted value. 507 * @throws InvalidDataConversionException If value cannot be converted. 508 */ 509 public ObjectMap getObjectMap(int index) { 510 return get(index, ObjectMap.class); 511 } 512 513 /** 514 * Shortcut for calling <code>get(index, ObjectList.<jk>class</jk>)</code>. 515 * 516 * @param index The index. 517 * @return The converted value. 518 * @throws InvalidDataConversionException If value cannot be converted. 519 */ 520 public ObjectList getObjectList(int index) { 521 return get(index, ObjectList.class); 522 } 523 524 /** 525 * Same as {@link #get(int,Class) get(int,Class)}, but the key is a slash-delimited path used to traverse entries in 526 * this POJO. 527 * 528 * <p> 529 * For example, the following code is equivalent: 530 * </p> 531 * <p class='bcode w800'> 532 * ObjectMap m = getObjectMap(); 533 * 534 * <jc>// Long way</jc> 535 * <jk>long</jk> l = m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>).getLong(<js>"baz"</js>); 536 * 537 * <jc>// Using this method</jc> 538 * <jk>long</jk> l = m.getAt(<js>"foo/bar/0/baz"</js>, <jk>long</jk>.<jk>class</jk>); 539 * </p> 540 * 541 * <p> 542 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 543 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 544 * 545 * @param path The path to the entry. 546 * @param type The class type. 547 * 548 * @param <T> The class type. 549 * @return The value, or <jk>null</jk> if the entry doesn't exist. 550 */ 551 public <T> T getAt(String path, Class<T> type) { 552 return getPojoRest().get(path, type); 553 } 554 555 /** 556 * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections. 557 * 558 * @param path The path to the entry. 559 * @param type The class type. 560 * @param args The class parameter types. 561 * 562 * @param <T> The class type. 563 * @return The value, or <jk>null</jk> if the entry doesn't exist. 564 */ 565 public <T> T getAt(String path, Type type, Type...args) { 566 return getPojoRest().get(path, type, args); 567 } 568 569 /** 570 * Same as {@link #set(int,Object) set(int,Object)}, but the key is a slash-delimited path used to traverse entries 571 * in this POJO. 572 * 573 * <p> 574 * For example, the following code is equivalent: 575 * </p> 576 * <p class='bcode w800'> 577 * ObjectMap m = getObjectMap(); 578 * 579 * <jc>// Long way</jc> 580 * m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>).put(<js>"baz"</js>, 123); 581 * 582 * <jc>// Using this method</jc> 583 * m.putAt(<js>"foo/bar/0/baz"</js>, 123); 584 * </p> 585 * 586 * <p> 587 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 588 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 589 * 590 * @param path The path to the entry. 591 * @param o The new value. 592 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 593 */ 594 public Object putAt(String path, Object o) { 595 return getPojoRest().put(path, o); 596 } 597 598 /** 599 * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays. 600 * 601 * <p> 602 * For example, the following code is equivalent: 603 * </p> 604 * <p class='bcode w800'> 605 * ObjectMap m = getObjectMap(); 606 * 607 * <jc>// Long way</jc> 608 * m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).append(123); 609 * 610 * <jc>// Using this method</jc> 611 * m.postAt(<js>"foo/bar"</js>, 123); 612 * </p> 613 * 614 * <p> 615 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 616 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 617 * 618 * @param path The path to the entry. 619 * @param o The new value. 620 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 621 */ 622 public Object postAt(String path, Object o) { 623 return getPojoRest().post(path, o); 624 } 625 626 /** 627 * Similar to {@link #remove(int) remove(int)},but the key is a slash-delimited path used to traverse entries in 628 * this POJO. 629 * 630 * <p> 631 * For example, the following code is equivalent: 632 * </p> 633 * <p class='bcode w800'> 634 * ObjectMap m = getObjectMap(); 635 * 636 * <jc>// Long way</jc> 637 * m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(1).remove(<js>"baz"</js>); 638 * 639 * <jc>// Using this method</jc> 640 * m.deleteAt(<js>"foo/bar/0/baz"</js>); 641 * </p> 642 * 643 * <p> 644 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 645 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 646 * 647 * @param path The path to the entry. 648 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 649 */ 650 public Object deleteAt(String path) { 651 return getPojoRest().delete(path); 652 } 653 654 /** 655 * Creates an {@link Iterable} with elements of the specified child type. 656 * 657 * <p> 658 * Attempts to convert the child objects to the correct type if they aren't already the correct type. 659 * 660 * <p> 661 * The <c>next()</c> method on the returned iterator may throw a {@link InvalidDataConversionException} if 662 * the next element cannot be converted to the specified type. 663 * 664 * <p> 665 * See {@link BeanSession#convertToType(Object, ClassMeta)} for a description of valid conversions. 666 * 667 * <h5 class='section'>Example:</h5> 668 * <p class='bcode w800'> 669 * <jc>// Iterate over a list of ObjectMaps.</jc> 670 * ObjectList l = <jk>new</jk> ObjectList(<js>"[{foo:'bar'},{baz:123}]"</js>); 671 * for (ObjectMap m : l.elements(ObjectMap.<jk>class</jk>)) { 672 * <jc>// Do something with m.</jc> 673 * } 674 * 675 * <jc>// Iterate over a list of ints.</jc> 676 * ObjectList l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>); 677 * for (Integer i : l.elements(Integer.<jk>class</jk>)) { 678 * <jc>// Do something with i.</jc> 679 * } 680 * 681 * <jc>// Iterate over a list of beans.</jc> 682 * <jc>// Automatically converts to beans.</jc> 683 * ObjectList l = <jk>new</jk> ObjectList(<js>"[{name:'John Smith',age:45}]"</js>); 684 * for (Person p : l.elements(Person.<jk>class</jk>)) { 685 * <jc>// Do something with p.</jc> 686 * } 687 * </p> 688 * 689 * @param <E> The child object type. 690 * @param childType The child object type. 691 * @return A new <c>Iterable</c> object over this list. 692 */ 693 public <E> Iterable<E> elements(final Class<E> childType) { 694 final Iterator<?> i = iterator(); 695 return new Iterable<E>() { 696 697 @Override /* Iterable */ 698 public Iterator<E> iterator() { 699 return new Iterator<E>() { 700 701 @Override /* Iterator */ 702 public boolean hasNext() { 703 return i.hasNext(); 704 } 705 706 @Override /* Iterator */ 707 public E next() { 708 return bs2().convertToType(i.next(), childType); 709 } 710 711 @Override /* Iterator */ 712 public void remove() { 713 i.remove(); 714 } 715 716 }; 717 } 718 }; 719 } 720 721 /** 722 * Returns the {@link ClassMeta} of the class of the object at the specified index. 723 * 724 * @param index An index into this list, zero-based. 725 * @return The data type of the object at the specified index, or <jk>null</jk> if the value is null. 726 */ 727 public ClassMeta<?> getClassMeta(int index) { 728 return bs2().getClassMetaForObject(get(index)); 729 } 730 731 private PojoRest getPojoRest() { 732 if (pojoRest == null) 733 pojoRest = new PojoRest(this); 734 return pojoRest; 735 } 736 737 /** 738 * Serialize this array to a string using the specified serializer. 739 * 740 * @param serializer The serializer to use to convert this object to a string. 741 * @return This object as a serialized string. 742 * @throws SerializeException If a problem occurred trying to convert the output. 743 */ 744 public String toString(WriterSerializer serializer) throws SerializeException { 745 return serializer.serialize(this); 746 } 747 748 /** 749 * Returns <jk>true</jk> if this list is unmodifiable. 750 * 751 * @return <jk>true</jk> if this list is unmodifiable. 752 */ 753 public boolean isUnmodifiable() { 754 return false; 755 } 756 757 /** 758 * Returns a modifiable copy of this list if it's unmodifiable. 759 * 760 * @return A modifiable copy of this list if it's unmodifiable, or this list if it is already modifiable. 761 */ 762 public ObjectList modifiable() { 763 if (isUnmodifiable()) 764 return new ObjectList(this); 765 return this; 766 } 767 768 /** 769 * Returns an unmodifiable copy of this list if it's modifiable. 770 * 771 * @return An unmodifiable copy of this list if it's modifiable, or this list if it is already unmodifiable. 772 */ 773 public ObjectList unmodifiable() { 774 if (this instanceof UnmodifiableObjectList) 775 return this; 776 return new UnmodifiableObjectList(this); 777 } 778 779 /** 780 * Serialize this array to JSON using the {@link JsonSerializer#DEFAULT} serializer. 781 */ 782 @Override /* Object */ 783 public String toString() { 784 try { 785 return this.toString(SimpleJsonSerializer.DEFAULT); 786 } catch (SerializeException e) { 787 return e.getLocalizedMessage(); 788 } 789 } 790 791 /** 792 * Convenience method for serializing this ObjectList to the specified Writer using the JsonSerializer.DEFAULT 793 * serializer. 794 * 795 * @param w The writer to send the serialized contents of this object. 796 * @throws IOException If a problem occurred trying to write to the writer. 797 * @throws SerializeException If a problem occurred trying to convert the output. 798 */ 799 public void serializeTo(Writer w) throws IOException, SerializeException { 800 JsonSerializer.DEFAULT.serialize(this); 801 } 802 803 /** 804 * Converts this object into the specified class type. 805 * 806 * <p> 807 * TODO - The current implementation is very inefficient. 808 * 809 * @param cm The class type to convert this object to. 810 * @return A converted object. 811 */ 812 public Object cast(ClassMeta<?> cm) { 813 try { 814 return JsonParser.DEFAULT.parse(SimpleJsonSerializer.DEFAULT.serialize(this), cm); 815 } catch (ParseException | SerializeException e) { 816 throw new RuntimeException(e); 817 } 818 } 819 820 private static final class UnmodifiableObjectList extends ObjectList { 821 private static final long serialVersionUID = 1L; 822 823 UnmodifiableObjectList(ObjectList contents) { 824 super(); 825 if (contents != null) { 826 for (Object e : this) { 827 super.add(e); 828 } 829 } 830 } 831 832 @Override /* List */ 833 public void add(int location, Object object) { 834 throw new UnsupportedOperationException("ObjectList is read-only."); 835 } 836 837 @Override /* List */ 838 public Object remove(int location) { 839 throw new UnsupportedOperationException("ObjectList is read-only."); 840 } 841 842 @Override /* List */ 843 public Object set(int location, Object object) { 844 throw new UnsupportedOperationException("ObjectList is read-only."); 845 } 846 847 @Override 848 public final boolean isUnmodifiable() { 849 return true; 850 } 851 } 852 853 BeanSession bs2() { 854 if (session == null) 855 session = BeanContext.DEFAULT.createBeanSession(); 856 return session; 857 } 858}