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