1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.juneau.collections;
18
19 import static org.apache.juneau.commons.utils.PredicateUtils.*;
20 import static org.apache.juneau.commons.utils.StringUtils.*;
21 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
22 import static org.apache.juneau.commons.utils.Utils.*;
23
24 import java.io.*;
25 import java.lang.reflect.*;
26 import java.util.*;
27 import java.util.function.*;
28
29 import org.apache.juneau.*;
30 import org.apache.juneau.commons.utils.*;
31 import org.apache.juneau.json.*;
32 import org.apache.juneau.marshaller.*;
33 import org.apache.juneau.objecttools.*;
34 import org.apache.juneau.parser.*;
35 import org.apache.juneau.serializer.*;
36
37 /**
38 * Java implementation of a JSON array.
39 *
40 * <p>
41 * An extension of {@link LinkedList}, so all methods available to in that class are also available to this class.
42 *
43 * <p>
44 * Note that the use of this class is optional for generating JSON. The serializers will accept any objects that implement the
45 * {@link Collection} interface. But this class provides some useful additional functionality when working with JSON
46 * models constructed from Java Collections Framework objects. For example, a constructor is provided for converting a
47 * JSON array string directly into a {@link List}. It also contains accessor methods for to avoid common typecasting
48 * when accessing elements in a list.
49 *
50 * <h5 class='section'>Example:</h5>
51 * <p class='bjava'>
52 * <jc>// Construct an empty List</jc>
53 * JsonList <jv>list</jv> = JsonList.<jsm>of</jsm>();
54 *
55 * <jc>// Construct a list of objects using various methods</jc>
56 * <jv>list</jv> = JsonList.<jsm>of</jsm>().a(<js>"foo"</js>).a(123).a(<jk>true</jk>);
57 * <jv>list</jv> = JsonList.<jsm>of</jsm>().a(<js>"foo"</js>, 123, <jk>true</jk>); <jc>// Equivalent</jc>
58 * <jv>list</jv> = JsonList.<jsm>of</jsm>(<js>"foo"</js>, 123, <jk>true</jk>); <jc>// Equivalent</jc>
59 *
60 * <jc>// Construct a list of integers from JSON</jc>
61 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>);
62 *
63 * <jc>// Construct a list of generic JsonMap objects from JSON</jc>
64 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
65 *
66 * <jc>// Construct a list of integers from XML</jc>
67 * String <jv>xml</jv> = <js>"<array><number>1</number><number>2</number><number>3</number></array>"</js>;
68 * <jv>list</jv> = JsonList.<jsm>of</jsm>(<jv>xml</jv>, XmlParser.<jsf>DEFAULT</jsf>);
69 * <jv>list</jv> = (List)XmlParser.<jsf>DEFAULT</jsf>.parse(<jv>xml</jv>); <jc>// Equivalent</jc>
70 * <jv>list</jv> = (List)XmlParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, <jv>xml</jv>); <jc>// Equivalent</jc>
71 * <jv>list</jv> = XmlParser.<jsf>DEFAULT</jsf>.parse(List.<jk>class</jk>, <jv>xml</jv>); <jc>// Equivalent</jc>
72 * <jv>list</jv> = XmlParser.<jsf>DEFAULT</jsf>.parse(JsonList.<jk>class</jk>, <jv>xml</jv>); <jc>// Equivalent</jc>
73 *
74 * <jc>// Construct JSON from JsonList</jc>
75 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
76 * String <jv>json</jv> = <jv>list</jv>.toString(); <jc>// Produces "[{foo:'bar'},{baz:'bing'}]"</jc>
77 * <jv>json</jv> = <jv>list</jv>.toString(JsonSerializer.<jsf>DEFAULT</jsf>); <jc>// Equivalent</jc>
78 * <jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>list</jv>); <jc>// Equivalent</jc>
79 *
80 * <jc>// Get one of the entries in the list as an Integer</jc>
81 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>);
82 * Integer <jv>integer</jv> = <jv>list</jv>.getInt(1);
83 * <jv>list</jv> = <jv>list</jv>.get(Integer.<jk>class</jk>, 1); <jc>// Equivalent</jc>
84 *
85 * <jc>// Get one of the entries in the list as an Float</jc>
86 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>);
87 * Float <jv>_float</jv> = <jv>list</jv>.getFloat(1); <jc>// Returns 2f </jc>
88 * <jv>_float</jv> = <jv>list</jv>.get(Float.<jk>class</jk>, 1); <jc>// Equivalent</jc>
89 *
90 * <jc>// Same as above, except converted to a String</jc>
91 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>);
92 * String <jv>string</jv> = <jv>list</jv>.getString(1); <jc>// Returns "2" </jc>
93 * <jv>string</jv> = <jv>list</jv>.get(String.<jk>class</jk>, 1); <jc>// Equivalent</jc>
94 *
95 * <jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc>
96 * <jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{name:'John Smith',age:45}]"</js>);
97 * Person <jv>person</jv> = <jv>list</jv>.get(Person.<jk>class</jk>, 0);
98 *
99 * <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 */
113 public 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 }