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.collections; 014 015import static org.apache.juneau.common.internal.ThrowableUtils.*; 016import static org.apache.juneau.internal.ConsumerUtils.*; 017 018import java.io.*; 019import java.lang.reflect.*; 020import java.util.*; 021import java.util.function.*; 022 023import org.apache.juneau.*; 024import org.apache.juneau.common.internal.*; 025import org.apache.juneau.json.*; 026import org.apache.juneau.marshaller.*; 027import org.apache.juneau.objecttools.*; 028import org.apache.juneau.parser.*; 029import org.apache.juneau.serializer.*; 030 031/** 032 * Java implementation of a JSON array. 033 * 034 * <p> 035 * An extension of {@link LinkedList}, so all methods available to in that class are also available to this class. 036 * 037 * <p> 038 * Note that the use of this class is optional for generating JSON. The serializers will accept any objects that implement the 039 * {@link Collection} interface. But this class provides some useful additional functionality when working with JSON 040 * models constructed from Java Collections Framework objects. For example, a constructor is provided for converting a 041 * JSON array string directly into a {@link List}. It also contains accessor methods for to avoid common typecasting 042 * when accessing elements in a list. 043 * 044 * <h5 class='section'>Example:</h5> 045 * <p class='bjava'> 046 * <jc>// Construct an empty List</jc> 047 * JsonList <jv>list</jv> = JsonList.<jsm>of</jsm>(); 048 * 049 * <jc>// Construct a list of objects using various methods</jc> 050 * <jv>list</jv> = JsonList.<jsm>of</jsm>().a(<js>"foo"</js>).a(123).a(<jk>true</jk>); 051 * <jv>list</jv> = JsonList.<jsm>of</jsm>().a(<js>"foo"</js>, 123, <jk>true</jk>); <jc>// Equivalent</jc> 052 * <jv>list</jv> = JsonList.<jsm>of</jsm>(<js>"foo"</js>, 123, <jk>true</jk>); <jc>// Equivalent</jc> 053 * 054 * <jc>// Construct a list of integers from JSON</jc> 055 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>); 056 * 057 * <jc>// Construct a list of generic JsonMap objects from JSON</jc> 058 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>); 059 * 060 * <jc>// Construct a list of integers from XML</jc> 061 * String <jv>xml</jv> = <js>"<array><number>1</number><number>2</number><number>3</number></array>"</js>; 062 * <jv>list</jv> = JsonList.<jsm>of</jsm>(<jv>xml</jv>, XmlParser.<jsf>DEFAULT</jsf>); 063 * <jv>list</jv> = (List)XmlParser.<jsf>DEFAULT</jsf>.parse(<jv>xml</jv>); <jc>// Equivalent</jc> 064 * <jv>list</jv> = (List)XmlParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, <jv>xml</jv>); <jc>// Equivalent</jc> 065 * <jv>list</jv> = XmlParser.<jsf>DEFAULT</jsf>.parse(List.<jk>class</jk>, <jv>xml</jv>); <jc>// Equivalent</jc> 066 * <jv>list</jv> = XmlParser.<jsf>DEFAULT</jsf>.parse(JsonList.<jk>class</jk>, <jv>xml</jv>); <jc>// Equivalent</jc> 067 * 068 * <jc>// Construct JSON from JsonList</jc> 069 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>); 070 * String <jv>json</jv> = <jv>list</jv>.toString(); <jc>// Produces "[{foo:'bar'},{baz:'bing'}]"</jc> 071 * <jv>json</jv> = <jv>list</jv>.toString(JsonSerializer.<jsf>DEFAULT</jsf>); <jc>// Equivalent</jc> 072 * <jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>list</jv>); <jc>// Equivalent</jc> 073 * 074 * <jc>// Get one of the entries in the list as an Integer</jc> 075 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>); 076 * Integer <jv>integer</jv> = <jv>list</jv>.getInt(1); 077 * <jv>list</jv> = <jv>list</jv>.get(Integer.<jk>class</jk>, 1); <jc>// Equivalent</jc> 078 * 079 * <jc>// Get one of the entries in the list as an Float</jc> 080 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>); 081 * Float <jv>_float</jv> = <jv>list</jv>.getFloat(1); <jc>// Returns 2f </jc> 082 * <jv>_float</jv> = <jv>list</jv>.get(Float.<jk>class</jk>, 1); <jc>// Equivalent</jc> 083 * 084 * <jc>// Same as above, except converted to a String</jc> 085 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>); 086 * String <jv>string</jv> = <jv>list</jv>.getString(1); <jc>// Returns "2" </jc> 087 * <jv>string</jv> = <jv>list</jv>.get(String.<jk>class</jk>, 1); <jc>// Equivalent</jc> 088 * 089 * <jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc> 090 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{name:'John Smith',age:45}]"</js>); 091 * Person <jv>person</jv> = <jv>list</jv>.get(Person.<jk>class</jk>, 0); 092 * 093 * <jc>// Iterate over a list of beans using the elements() method</jc> 094 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{name:'John Smith',age:45}]"</js>); 095 * <jk>for</jk> (Person <jv>person</jv> : <jv>list</jv>.elements(Person.<jk>class</jk>) { 096 * <jc>// Do something with p</jc> 097 * } 098 * </p> 099 * 100 * <h5 class='section'>Notes:</h5><ul> 101 * <li class='warn'>This class is not thread safe. 102 * </ul> 103 * 104 * <h5 class='section'>See Also:</h5><ul> 105 106 * </ul> 107 * 108 * @serial exclude 109 */ 110public class JsonList extends LinkedList<Object> { 111 112 //----------------------------------------------------------------------------------------------------------------- 113 // Static 114 //----------------------------------------------------------------------------------------------------------------- 115 116 private static final long serialVersionUID = 1L; 117 118 /** 119 * Parses a string that can consist of either a JSON array or comma-delimited list. 120 * 121 * <p> 122 * The type of string is auto-detected. 123 * 124 * @param s The string to parse. 125 * @return The parsed string. 126 * @throws ParseException Malformed input encountered. 127 */ 128 public static JsonList ofJsonOrCdl(String s) throws ParseException { 129 if (StringUtils.isEmpty(s)) 130 return null; 131 if (! StringUtils.isJsonArray(s, true)) 132 return new JsonList((Object[])StringUtils.split(s.trim(), ',')); 133 return new JsonList(s); 134 } 135 136 //----------------------------------------------------------------------------------------------------------------- 137 // Instance 138 //----------------------------------------------------------------------------------------------------------------- 139 140 transient BeanSession session = null; 141 private transient ObjectRest objectRest; 142 143 /** 144 * An empty read-only JsonList. 145 * 146 * @serial exclude 147 */ 148 public static final JsonList EMPTY_LIST = new JsonList() { 149 private static final long serialVersionUID = 1L; 150 151 @Override /* List */ 152 public void add(int location, Object object) { 153 throw new UnsupportedOperationException("Not supported on read-only object."); 154 } 155 156 @Override /* List */ 157 public ListIterator<Object> listIterator(final int location) { 158 return Collections.emptyList().listIterator(location); 159 } 160 161 @Override /* List */ 162 public Object remove(int location) { 163 throw new UnsupportedOperationException("Not supported on read-only object."); 164 } 165 166 @Override /* List */ 167 public Object set(int location, Object object) { 168 throw new UnsupportedOperationException("Not supported on read-only object."); 169 } 170 171 @Override /* List */ 172 public List<Object> subList(int start, int end) { 173 return Collections.emptyList().subList(start, end); 174 } 175 }; 176 177 //------------------------------------------------------------------------------------------------------------------ 178 // Constructors 179 //------------------------------------------------------------------------------------------------------------------ 180 181 /** 182 * Construct an empty list. 183 */ 184 public JsonList() {} 185 186 /** 187 * Construct an empty list with the specified bean context. 188 * 189 * @param session The bean session to use for creating beans. 190 */ 191 public JsonList(BeanSession session) { 192 super(); 193 this.session = session; 194 } 195 196 /** 197 * Construct a list initialized with the specified list. 198 * 199 * @param copyFrom 200 * The list to copy. 201 * <br>Can be <jk>null</jk>. 202 */ 203 public JsonList(Collection<?> copyFrom) { 204 super(copyFrom); 205 } 206 207 /** 208 * Construct a list initialized with the specified JSON. 209 * 210 * @param json 211 * The JSON text to parse. 212 * <br>Can be normal or simplified JSON. 213 * @throws ParseException Malformed input encountered. 214 */ 215 public JsonList(CharSequence json) throws ParseException { 216 this(json, JsonParser.DEFAULT); 217 } 218 219 /** 220 * Construct a list initialized with the specified string. 221 * 222 * @param in 223 * The input being parsed. 224 * <br>Can be <jk>null</jk>. 225 * @param p 226 * The parser to use to parse the input. 227 * <br>If <jk>null</jk>, uses {@link JsonParser}. 228 * @throws ParseException Malformed input encountered. 229 */ 230 public JsonList(CharSequence in, Parser p) throws ParseException { 231 this(p == null ? BeanContext.DEFAULT_SESSION : p.getBeanContext().getSession()); 232 if (p == null) 233 p = JsonParser.DEFAULT; 234 if (in != null) 235 p.parseIntoCollection(in, this, bs().object()); 236 } 237 238 /** 239 * Construct a list initialized with the specified reader containing JSON. 240 * 241 * @param json 242 * The reader containing JSON text to parse. 243 * <br>Can contain normal or simplified JSON. 244 * @throws ParseException Malformed input encountered. 245 */ 246 public JsonList(Reader json) throws ParseException { 247 parse(json, JsonParser.DEFAULT); 248 } 249 250 /** 251 * Construct a list initialized with the specified string. 252 * 253 * @param in 254 * The reader containing the input being parsed. 255 * <br>Can contain normal or simplified JSON. 256 * @param p 257 * The parser to use to parse the input. 258 * <br>If <jk>null</jk>, uses {@link JsonParser}. 259 * @throws ParseException Malformed input encountered. 260 */ 261 public JsonList(Reader in, Parser p) throws ParseException { 262 this(p == null ? BeanContext.DEFAULT_SESSION : p.getBeanContext().getSession()); 263 parse(in, p); 264 } 265 266 /** 267 * Construct a list initialized with the contents. 268 * 269 * @param entries The entries to add to this list. 270 */ 271 public JsonList(Object... entries) { 272 super(); 273 Collections.addAll(this, entries); 274 } 275 276 //------------------------------------------------------------------------------------------------------------------ 277 // Creators 278 //------------------------------------------------------------------------------------------------------------------ 279 280 /** 281 * Construct an empty list. 282 * 283 * @return An empty list. 284 */ 285 public static JsonList create() { 286 return new JsonList(); 287 } 288 289 /** 290 * Construct a list initialized with the specified list. 291 * 292 * @param values 293 * The list to copy. 294 * <br>Can be <jk>null</jk>. 295 * @return A new list or <jk>null</jk> if the list was <jk>null</jk>. 296 */ 297 public static JsonList of(Collection<?> values) { 298 return values == null ? null : new JsonList(values); 299 } 300 301 /** 302 * Convenience method for creating a list of collection objects. 303 * 304 * @param values The initial values. 305 * @return A new list. 306 */ 307 public static JsonList ofCollections(Collection<?>...values) { 308 JsonList l = new JsonList(); 309 for (Collection<?> v : values) 310 l.add(v); 311 return l; 312 } 313 314 /** 315 * Convenience method for creating a list of array objects. 316 * 317 * @param values The initial values. 318 * @return A new list. 319 */ 320 public static JsonList ofArrays(Object[]...values) { 321 JsonList l = new JsonList(); 322 for (Object[] v : values) 323 l.add(v); 324 return l; 325 } 326 327 /** 328 * Construct a list initialized with the specified JSON string. 329 * 330 * @param json 331 * The JSON text to parse. 332 * <br>Can be normal or simplified JSON. 333 * @return A new list or <jk>null</jk> if the string was null. 334 * @throws ParseException Malformed input encountered. 335 */ 336 public static JsonList ofJson(CharSequence json) throws ParseException { 337 return json == null ? null : new JsonList(json); 338 } 339 340 /** 341 * Construct a list initialized with the specified string. 342 * 343 * @param in 344 * The input being parsed. 345 * <br>Can be <jk>null</jk>. 346 * @param p 347 * The parser to use to parse the input. 348 * <br>If <jk>null</jk>, uses {@link JsonParser}. 349 * @return A new list or <jk>null</jk> if the input was <jk>null</jk>. 350 * @throws ParseException Malformed input encountered. 351 */ 352 public static JsonList ofText(CharSequence in, Parser p) throws ParseException { 353 return in == null ? null : new JsonList(in, p); 354 } 355 356 /** 357 * Construct a list initialized with the specified reader containing JSON. 358 * 359 * @param json 360 * The reader containing JSON text to parse. 361 * <br>Can contain normal or simplified JSON. 362 * @return A new list or <jk>null</jk> if the input was <jk>null</jk>. 363 * @throws ParseException Malformed input encountered. 364 */ 365 public static JsonList ofJson(Reader json) throws ParseException { 366 return json == null ? null : new JsonList(json); 367 } 368 369 /** 370 * Construct a list initialized with the specified string. 371 * 372 * @param in 373 * The reader containing the input being parsed. 374 * <br>Can contain normal or simplified JSON. 375 * @param p 376 * The parser to use to parse the input. 377 * <br>If <jk>null</jk>, uses {@link JsonParser}. 378 * @return A new list or <jk>null</jk> if the input was <jk>null</jk>. 379 * @throws ParseException Malformed input encountered. 380 */ 381 public static JsonList ofText(Reader in, Parser p) throws ParseException { 382 return in == null ? null : new JsonList(in); 383 } 384 385 /** 386 * Construct a list initialized with the specified values. 387 * 388 * @param values The values to add to this list. 389 * @return A new list, never <jk>null</jk>. 390 */ 391 public static JsonList of(Object... values) { 392 return new JsonList(values); 393 } 394 395 //------------------------------------------------------------------------------------------------------------------ 396 // Initializers 397 //------------------------------------------------------------------------------------------------------------------ 398 399 /** 400 * Override the default bean session used for converting POJOs. 401 * 402 * <p> 403 * Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases. 404 * 405 * <p> 406 * Useful if you're serializing/parsing beans with transforms defined. 407 * 408 * @param session The new bean session. 409 * @return This object. 410 */ 411 public JsonList session(BeanSession session) { 412 this.session = session; 413 return this; 414 } 415 416 //------------------------------------------------------------------------------------------------------------------ 417 // Appenders 418 //------------------------------------------------------------------------------------------------------------------ 419 420 /** 421 * Adds the value to this list. 422 * 423 * @param value The value to add to this list. 424 * @return This object. 425 */ 426 public JsonList append(Object value) { 427 add(value); 428 return this; 429 } 430 431 /** 432 * Adds all the values in the specified array to this list. 433 * 434 * @param values The values to add to this list. 435 * @return This object. 436 */ 437 public JsonList append(Object...values) { 438 Collections.addAll(this, values); 439 return this; 440 } 441 442 /** 443 * Adds all the values in the specified collection to this list. 444 * 445 * @param values The values to add to this list. 446 * @return This object. 447 */ 448 public JsonList append(Collection<?> values) { 449 if (values != null) 450 addAll(values); 451 return this; 452 } 453 454 /** 455 * Adds an entry to this list if the boolean flag is <jk>true</jk>. 456 * 457 * @param flag The boolean flag. 458 * @param value The value to add. 459 * @return This object. 460 */ 461 public JsonList appendIf(boolean flag, Object value) { 462 if (flag) 463 append(value); 464 return this; 465 } 466 467 /** 468 * Adds all the entries in the specified collection to this list in reverse order. 469 * 470 * @param values The collection to add to this list. 471 * @return This object. 472 */ 473 public JsonList appendReverse(List<?> values) { 474 for (ListIterator<?> i = values.listIterator(values.size()); i.hasPrevious();) 475 add(i.previous()); 476 return this; 477 } 478 479 /** 480 * Adds the contents of the array to the list in reverse order. 481 * 482 * <p> 483 * i.e. add values from the array from end-to-start order to the end of the list. 484 * 485 * @param values The collection to add to this list. 486 * @return This object. 487 */ 488 public JsonList appendReverse(Object...values) { 489 for (int i = values.length - 1; i >= 0; i--) 490 add(values[i]); 491 return this; 492 } 493 494 /** 495 * Add if predicate matches. 496 * 497 * @param <T> The type being tested. 498 * @param test The predicate to match against. 499 * @param value The value to add if the predicate matches. 500 * @return This object. 501 */ 502 public <T> JsonList appendIf(Predicate<T> test, T value) { 503 return appendIf(test(test, value), value); 504 } 505 506 //------------------------------------------------------------------------------------------------------------------ 507 // Retrievers 508 //------------------------------------------------------------------------------------------------------------------ 509 510 /** 511 * Get the entry at the specified index, converted to the specified type. 512 * 513 * <p> 514 * This is the preferred get method for simple types. 515 * 516 * <h5 class='section'>Examples:</h5> 517 * <p class='bjava'> 518 * JsonList <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"..."</js>); 519 * 520 * <jc>// Value converted to a string.</jc> 521 * String <jv>string</jv> = <jv>list</jv>.get(1, String.<jk>class</jk>); 522 * 523 * <jc>// Value converted to a bean.</jc> 524 * MyBean <jv>bean</jv> = <jv>list</jv>.get(2, MyBean.<jk>class</jk>); 525 * 526 * <jc>// Value converted to a bean array.</jc> 527 * MyBean[] <jv>beanArray</jv> = <jv>list</jv>.get(3, MyBean[].<jk>class</jk>); 528 * 529 * <jc>// Value converted to a linked-list of objects.</jc> 530 * List <jv>list2</jv> = <jv>list</jv>.get(4, LinkedList.<jk>class</jk>); 531 * 532 * <jc>// Value converted to a map of object keys/values.</jc> 533 * Map <jv>map</jv> = <jv>list</jv>.get(5, TreeMap.<jk>class</jk>); 534 * </p> 535 * 536 * <p> 537 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 538 * 539 * @param index The index into this list. 540 * @param type The type of object to convert the entry to. 541 * @param <T> The type of object to convert the entry to. 542 * @return The converted entry. 543 */ 544 public <T> T get(int index, Class<T> type) { 545 return bs().convertToType(get(index), type); 546 } 547 548 /** 549 * Get the entry at the specified index, converted to the specified type. 550 * 551 * <p> 552 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 553 * 554 * <h5 class='section'>Examples:</h5> 555 * <p class='bjava'> 556 * JsonList <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"..."</js>); 557 * 558 * <jc>// Value converted to a linked-list of strings.</jc> 559 * List<String> <jv>list1</jv> = <jv>list</jv>.get(1, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 560 * 561 * <jc>// Value converted to a linked-list of beans.</jc> 562 * List<MyBean> <jv>list2</jv> = <jv>list</jv>.get(2, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 563 * 564 * <jc>// Value converted to a linked-list of linked-lists of strings.</jc> 565 * List<List<String>> <jv>list3</jv> = <jv>list</jv>.get(3, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 566 * 567 * <jc>// Value converted to a map of string keys/values.</jc> 568 * Map<String,String> <jv>map1</jv> = <jv>list</jv>.get(4, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 569 * 570 * <jc>// Value converted to a map containing string keys and values of lists containing beans.</jc> 571 * Map<String,List<MyBean>> <jv>map2</jv> = <jv>list</jv>.get(5, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 572 * </p> 573 * 574 * <p> 575 * <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. 576 * 577 * <p> 578 * <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value types. 579 * 580 * <p> 581 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 582 * 583 * <p> 584 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 585 * 586 * @param index The index into this list. 587 * @param type The type of object to convert the entry to. 588 * @param args The type arguments of the type to convert the entry to. 589 * @param <T> The type of object to convert the entry to. 590 * @return The converted entry. 591 */ 592 public <T> T get(int index, Type type, Type...args) { 593 return bs().convertToType(get(index), type, args); 594 } 595 596 /** 597 * Shortcut for calling <code>get(index, String.<jk>class</jk>)</code>. 598 * 599 * @param index The index. 600 * @return The converted value. 601 */ 602 public String getString(int index) { 603 return get(index, String.class); 604 } 605 606 /** 607 * Shortcut for calling <code>get(index, Integer.<jk>class</jk>)</code>. 608 * 609 * @param index The index. 610 * @return The converted value. 611 * @throws InvalidDataConversionException If value cannot be converted. 612 */ 613 public Integer getInt(int index) { 614 return get(index, Integer.class); 615 } 616 617 /** 618 * Shortcut for calling <code>get(index, Boolean.<jk>class</jk>)</code>. 619 * 620 * @param index The index. 621 * @return The converted value. 622 * @throws InvalidDataConversionException If value cannot be converted. 623 */ 624 public Boolean getBoolean(int index) { 625 return get(index, Boolean.class); 626 } 627 628 /** 629 * Shortcut for calling <code>get(index, Long.<jk>class</jk>)</code>. 630 * 631 * @param index The index. 632 * @return The converted value. 633 * @throws InvalidDataConversionException If value cannot be converted. 634 */ 635 public Long getLong(int index) { 636 return get(index, Long.class); 637 } 638 639 /** 640 * Shortcut for calling <code>get(index, JsonMap.<jk>class</jk>)</code>. 641 * 642 * @param index The index. 643 * @return The converted value. 644 * @throws InvalidDataConversionException If value cannot be converted. 645 */ 646 public JsonMap getMap(int index) { 647 return get(index, JsonMap.class); 648 } 649 650 /** 651 * Same as {@link #getMap(int)} except converts the keys and values to the specified types. 652 * 653 * @param <K> The key type class. 654 * @param <V> The value type class. 655 * @param index The index. 656 * @param keyType The key type class. 657 * @param valType The value type class. 658 * @return The converted value. 659 * @throws InvalidDataConversionException If value cannot be converted. 660 */ 661 public <K,V> Map<K,V> getMap(int index, Class<K> keyType, Class<V> valType) { 662 return bs().convertToType(get(index), Map.class, keyType, valType); 663 } 664 665 /** 666 * Shortcut for calling <code>get(index, JsonList.<jk>class</jk>)</code>. 667 * 668 * @param index The index. 669 * @return The converted value. 670 * @throws InvalidDataConversionException If value cannot be converted. 671 */ 672 public JsonList getList(int index) { 673 return get(index, JsonList.class); 674 } 675 676 /** 677 * Same as {@link #getList(int)} except converts the elements to the specified types. 678 * 679 * @param <E> The element type. 680 * @param index The index. 681 * @param elementType The element type class. 682 * @return The converted value. 683 * @throws InvalidDataConversionException If value cannot be converted. 684 */ 685 public <E> List<E> getList(int index, Class<E> elementType) { 686 return bs().convertToType(get(index), List.class, elementType); 687 } 688 689 //------------------------------------------------------------------------------------------------------------------ 690 // POJO REST methods. 691 //------------------------------------------------------------------------------------------------------------------ 692 693 /** 694 * Same as {@link #get(int,Class) get(int,Class)}, but the key is a slash-delimited path used to traverse entries in 695 * this POJO. 696 * 697 * <p> 698 * For example, the following code is equivalent: 699 * </p> 700 * <p class='bjava'> 701 * JsonList <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"..."</js>); 702 * 703 * <jc>// Long way</jc> 704 * <jk>long</jk> <jv>long1</jv> = <jv>list</jv>.getMap(<js>"0"</js>).getLong(<js>"baz"</js>); 705 * 706 * <jc>// Using this method</jc> 707 * <jk>long</jk> <jv>long2</jv> = <jv>list</jv>.getAt(<js>"0/baz"</js>, <jk>long</jk>.<jk>class</jk>); 708 * </p> 709 * 710 * <p> 711 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various 712 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). 713 * 714 * @param path The path to the entry. 715 * @param type The class type. 716 * 717 * @param <T> The class type. 718 * @return The value, or <jk>null</jk> if the entry doesn't exist. 719 */ 720 public <T> T getAt(String path, Class<T> type) { 721 return getObjectRest().get(path, type); 722 } 723 724 /** 725 * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections. 726 * 727 * @param path The path to the entry. 728 * @param type The class type. 729 * @param args The class parameter types. 730 * 731 * @param <T> The class type. 732 * @return The value, or <jk>null</jk> if the entry doesn't exist. 733 */ 734 public <T> T getAt(String path, Type type, Type...args) { 735 return getObjectRest().get(path, type, args); 736 } 737 738 /** 739 * Same as {@link #set(int,Object) set(int,Object)}, but the key is a slash-delimited path used to traverse entries 740 * in this POJO. 741 * 742 * <p> 743 * For example, the following code is equivalent: 744 * </p> 745 * <p class='bjava'> 746 * JsonList <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"..."</js>); 747 * 748 * <jc>// Long way</jc> 749 * <jv>list</jv>.getMap(<js>"0"</js>).put(<js>"baz"</js>, 123); 750 * 751 * <jc>// Using this method</jc> 752 * <jv>list</jv>.putAt(<js>"0/baz"</js>, 123); 753 * </p> 754 * 755 * <p> 756 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various 757 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). 758 * 759 * @param path The path to the entry. 760 * @param o The new value. 761 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 762 */ 763 public Object putAt(String path, Object o) { 764 return getObjectRest().put(path, o); 765 } 766 767 /** 768 * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays. 769 * 770 * <p> 771 * For example, the following code is equivalent: 772 * </p> 773 * <p class='bjava'> 774 * JsonList <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"..."</js>); 775 * 776 * <jc>// Long way</jc> 777 * <jv>list</jv>.getMap(0).getList(<js>"bar"</js>).append(123); 778 * 779 * <jc>// Using this method</jc> 780 * <jv>list</jv>.postAt(<js>"0/bar"</js>, 123); 781 * </p> 782 * 783 * <p> 784 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various 785 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). 786 * 787 * @param path The path to the entry. 788 * @param o The new value. 789 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 790 */ 791 public Object postAt(String path, Object o) { 792 return getObjectRest().post(path, o); 793 } 794 795 /** 796 * Similar to {@link #remove(int) remove(int)},but the key is a slash-delimited path used to traverse entries in 797 * this POJO. 798 * 799 * <p> 800 * For example, the following code is equivalent: 801 * </p> 802 * <p class='bjava'> 803 * JsonList <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"..."</js>); 804 * 805 * <jc>// Long way</jc> 806 * <jv>list</jv>.getMap(0).getList(<js>"bar"</js>).delete(0); 807 * 808 * <jc>// Using this method</jc> 809 * <jv>list</jv>.deleteAt(<js>"0/bar/0"</js>); 810 * </p> 811 * 812 * <p> 813 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various 814 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). 815 * 816 * @param path The path to the entry. 817 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 818 */ 819 public Object deleteAt(String path) { 820 return getObjectRest().delete(path); 821 } 822 823 //------------------------------------------------------------------------------------------------------------------ 824 // Other methods 825 //------------------------------------------------------------------------------------------------------------------ 826 827 /** 828 * Returns the {@link BeanSession} currently associated with this list. 829 * 830 * @return The {@link BeanSession} currently associated with this list. 831 */ 832 public BeanSession getBeanSession() { 833 return session; 834 } 835 836 /** 837 * Sets the {@link BeanSession} currently associated with this list. 838 * 839 * @param value The {@link BeanSession} currently associated with this list. 840 * @return This object. 841 */ 842 public JsonList setBeanSession(BeanSession value) { 843 this.session = value; 844 return this; 845 } 846 847 /** 848 * Creates an {@link Iterable} with elements of the specified child type. 849 * 850 * <p> 851 * Attempts to convert the child objects to the correct type if they aren't already the correct type. 852 * 853 * <p> 854 * The <c>next()</c> method on the returned iterator may throw a {@link InvalidDataConversionException} if 855 * the next element cannot be converted to the specified type. 856 * 857 * <p> 858 * See {@link BeanSession#convertToType(Object, ClassMeta)} for a description of valid conversions. 859 * 860 * <h5 class='section'>Example:</h5> 861 * <p class='bjava'> 862 * <jc>// Iterate over a list of JsonMaps.</jc> 863 * JsonList <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{foo:'bar'},{baz:123}]"</js>); 864 * <jk>for</jk> (JsonMap <jv>map</jv> : <jv>list</jv>.elements(JsonMap.<jk>class</jk>)) { 865 * <jc>// Do something with map.</jc> 866 * } 867 * 868 * <jc>// Iterate over a list of ints.</jc> 869 * JsonList <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>); 870 * <jk>for</jk> (Integer <jv>i</jv> : <jv>list</jv>.elements(Integer.<jk>class</jk>)) { 871 * <jc>// Do something with i.</jc> 872 * } 873 * 874 * <jc>// Iterate over a list of beans.</jc> 875 * <jc>// Automatically converts to beans.</jc> 876 * JsonList <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{name:'John Smith',age:45}]"</js>); 877 * <jk>for</jk> (Person <jv>p</jv> : <jv>list</jv>.elements(Person.<jk>class</jk>)) { 878 * <jc>// Do something with p.</jc> 879 * } 880 * </p> 881 * 882 * @param <E> The child object type. 883 * @param childType The child object type. 884 * @return A new <c>Iterable</c> object over this list. 885 */ 886 public <E> Iterable<E> elements(final Class<E> childType) { 887 final Iterator<?> i = iterator(); 888 return new Iterable<>() { 889 890 @Override /* Iterable */ 891 public Iterator<E> iterator() { 892 return new Iterator<>() { 893 894 @Override /* Iterator */ 895 public boolean hasNext() { 896 return i.hasNext(); 897 } 898 899 @Override /* Iterator */ 900 public E next() { 901 return bs().convertToType(i.next(), childType); 902 } 903 904 @Override /* Iterator */ 905 public void remove() { 906 i.remove(); 907 } 908 909 }; 910 } 911 }; 912 } 913 914 /** 915 * Returns the {@link ClassMeta} of the class of the object at the specified index. 916 * 917 * @param index An index into this list, zero-based. 918 * @return The data type of the object at the specified index, or <jk>null</jk> if the value is null. 919 */ 920 public ClassMeta<?> getClassMeta(int index) { 921 return bs().getClassMetaForObject(get(index)); 922 } 923 924 /** 925 * Serialize this array to a string using the specified serializer. 926 * 927 * @param serializer The serializer to use to convert this object to a string. 928 * @return This object as a serialized string. 929 */ 930 public String asString(WriterSerializer serializer) { 931 return serializer.toString(this); 932 } 933 934 /** 935 * Serialize this array to Simplified JSON. 936 * 937 * @return This object as a serialized string. 938 */ 939 public String asString() { 940 return Json5Serializer.DEFAULT.toString(this); 941 } 942 943 /** 944 * Returns <jk>true</jk> if this list is unmodifiable. 945 * 946 * @return <jk>true</jk> if this list is unmodifiable. 947 */ 948 public boolean isUnmodifiable() { 949 return false; 950 } 951 952 /** 953 * Returns a modifiable copy of this list if it's unmodifiable. 954 * 955 * @return A modifiable copy of this list if it's unmodifiable, or this list if it is already modifiable. 956 */ 957 public JsonList modifiable() { 958 if (isUnmodifiable()) 959 return new JsonList(this); 960 return this; 961 } 962 963 /** 964 * Returns an unmodifiable copy of this list if it's modifiable. 965 * 966 * @return An unmodifiable copy of this list if it's modifiable, or this list if it is already unmodifiable. 967 */ 968 public JsonList unmodifiable() { 969 if (this instanceof UnmodifiableJsonList) 970 return this; 971 return new UnmodifiableJsonList(this); 972 } 973 974 /** 975 * Convenience method for serializing this JsonList to the specified Writer using the JsonSerializer.DEFAULT 976 * serializer. 977 * 978 * @param w The writer to send the serialized contents of this object. 979 * @return This object. 980 * @throws IOException If a problem occurred trying to write to the writer. 981 * @throws SerializeException If a problem occurred trying to convert the output. 982 */ 983 public JsonList writeTo(Writer w) throws IOException, SerializeException { 984 JsonSerializer.DEFAULT.serialize(this, w); 985 return this; 986 } 987 988 /** 989 * Converts this object into the specified class type. 990 * 991 * <p> 992 * TODO - The current implementation is very inefficient. 993 * 994 * @param cm The class type to convert this object to. 995 * @return A converted object. 996 */ 997 public Object cast(ClassMeta<?> cm) { 998 try { 999 return JsonParser.DEFAULT.parse(Json5Serializer.DEFAULT.serialize(this), cm); 1000 } catch (ParseException | SerializeException e) { 1001 throw asRuntimeException(e); 1002 } 1003 } 1004 1005 //------------------------------------------------------------------------------------------------------------------ 1006 // Utility methods 1007 //------------------------------------------------------------------------------------------------------------------ 1008 1009 private void parse(Reader r, Parser p) throws ParseException { 1010 if (p == null) 1011 p = JsonParser.DEFAULT; 1012 p.parseIntoCollection(r, this, bs().object()); 1013 } 1014 1015 private ObjectRest getObjectRest() { 1016 if (objectRest == null) 1017 objectRest = new ObjectRest(this); 1018 return objectRest; 1019 } 1020 1021 BeanSession bs() { 1022 if (session == null) 1023 session = BeanContext.DEFAULT_SESSION; 1024 return session; 1025 } 1026 1027 private static final class UnmodifiableJsonList extends JsonList { 1028 private static final long serialVersionUID = 1L; 1029 1030 UnmodifiableJsonList(JsonList contents) { 1031 super(); 1032 if (contents != null) 1033 this.forEach(x -> super.add(x)); 1034 } 1035 1036 @Override /* List */ 1037 public void add(int location, Object object) { 1038 throw new UnsupportedOperationException("Not supported on read-only object."); 1039 } 1040 1041 @Override /* List */ 1042 public Object remove(int location) { 1043 throw new UnsupportedOperationException("Not supported on read-only object."); 1044 } 1045 1046 @Override /* List */ 1047 public Object set(int location, Object object) { 1048 throw new UnsupportedOperationException("Not supported on read-only object."); 1049 } 1050 1051 @Override 1052 public final boolean isUnmodifiable() { 1053 return true; 1054 } 1055 } 1056 1057 //------------------------------------------------------------------------------------------------------------------ 1058 // Overridden methods. 1059 //------------------------------------------------------------------------------------------------------------------ 1060 1061 /** 1062 * A synonym for {@link #toString()} 1063 * 1064 * @return This object as a JSON string. 1065 */ 1066 public String asJson() { 1067 return toString(); 1068 } 1069 1070 @Override /* Object */ 1071 public String toString() { 1072 return Json5.of(this); 1073 } 1074}