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