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