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.marshall.json5;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021import static org.apache.juneau.commons.utils.ThrowableUtils.*;
022
023import java.io.*;
024import java.util.*;
025import java.util.function.*;
026
027import org.apache.juneau.commons.utils.*;
028import org.apache.juneau.marshall.*;
029import org.apache.juneau.marshall.collections.*;
030import org.apache.juneau.marshall.marshaller.*;
031import org.apache.juneau.marshall.parser.*;
032import org.apache.juneau.marshall.serializer.*;
033
034/**
035 * JSON5-flavored {@link MarshalledList}.
036 *
037 * <p>
038 * Carries the JSON5-specific surface that historically lived on {@link JsonList}: a JSON5
039 * {@link #toString()}, JSON5 default-parser constructors, and convenience methods for working with
040 * JSON5 array text.
041 *
042 * <p>
043 * For strict (RFC 8259) JSON behavior, use {@link JsonList}. For a neutral, marshaller-agnostic
044 * base with no language coupling, use {@link MarshalledList}.
045 *
046 * <h5 class='section'>Example:</h5>
047 * <p class='bjava'>
048 *    <jc>// Construct an empty list</jc>
049 *    Json5List <jv>list</jv> = Json5List.<jsm>of</jsm>();
050 *
051 *    <jc>// Construct a list of generic Json5Map objects from JSON5</jc>
052    *    <jv>list</jv> = Json5List.<jsm>ofString</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
053 *
054 *    <jc>// JSON5 round-trip via toString()</jc>
055 *    String <jv>s</jv> = <jv>list</jv>.toString();  <jc>// "[{foo:'bar'},{baz:'bing'}]"</jc>
056 * </p>
057 *
058 * <h5 class='section'>Notes:</h5><ul>
059 *    <li class='warn'>This class is not thread safe.
060 * </ul>
061 *
062 * @serial exclude
063 */
064@SuppressWarnings({
065   "java:S110", // Class has many fields, acceptable for collection implementation
066   "java:S1206", // Inherits equals/hashCode from MarshalledList; List equality is element-based
067   "java:S2160", // equals() inherited from MarshalledList; element-based equality is correct
068   "java:S9149" // Per-format static factories intentionally shadow the parent's.
069})
070public class Json5List extends MarshalledList {
071   private static class Unmodifiable extends Json5List {
072      private static final long serialVersionUID = 1L;
073
074      @SuppressWarnings({
075         "synthetic-access" // Access to outer class members is intentional
076      })
077      Unmodifiable(Json5List contents) {
078         if (nn(contents))
079            contents.forEach(super::add);
080      }
081
082      @Override /* Overridden from List */
083      public boolean add(Object object) {
084         throw uoroex();
085      }
086
087      @Override /* Overridden from List */
088      public void add(int location, Object object) {
089         throw uoroex();
090      }
091
092      @Override /* Overridden from List */
093      public boolean addAll(Collection<?> c) {
094         throw uoroex();
095      }
096
097      @Override /* Overridden from List */
098      public boolean addAll(int location, Collection<?> c) {
099         throw uoroex();
100      }
101
102      @Override /* Overridden from Deque */
103      public void addFirst(Object object) {
104         throw uoroex();
105      }
106
107      @Override /* Overridden from Deque */
108      public void addLast(Object object) {
109         throw uoroex();
110      }
111
112      @Override /* Overridden from List */
113      public void clear() {
114         throw uoroex();
115      }
116
117      @Override
118      public boolean isUnmodifiable() { return true; }
119
120      @Override /* Overridden from List */
121      public Iterator<Object> iterator() {
122         return readOnlyIterator(super.iterator());
123      }
124
125      @Override /* Overridden from List */
126      public ListIterator<Object> listIterator() {
127         return readOnlyListIterator(super.listIterator());
128      }
129
130      @Override /* Overridden from List */
131      public ListIterator<Object> listIterator(int location) {
132         return readOnlyListIterator(super.listIterator(location));
133      }
134
135      @Override /* Overridden from Queue */
136      public boolean offer(Object object) {
137         throw uoroex();
138      }
139
140      @Override /* Overridden from Deque */
141      public boolean offerFirst(Object object) {
142         throw uoroex();
143      }
144
145      @Override /* Overridden from Deque */
146      public boolean offerLast(Object object) {
147         throw uoroex();
148      }
149
150      @Override /* Overridden from Queue */
151      public Object poll() {
152         throw uoroex();
153      }
154
155      @Override /* Overridden from Deque */
156      public Object pollFirst() {
157         throw uoroex();
158      }
159
160      @Override /* Overridden from Deque */
161      public Object pollLast() {
162         throw uoroex();
163      }
164
165      @Override /* Overridden from Deque */
166      public Object pop() {
167         throw uoroex();
168      }
169
170      @Override /* Overridden from Deque */
171      public void push(Object object) {
172         throw uoroex();
173      }
174
175      @Override /* Overridden from Queue */
176      public Object remove() {
177         throw uoroex();
178      }
179
180      @Override /* Overridden from List */
181      public Object remove(int location) {
182         throw uoroex();
183      }
184
185      @Override /* Overridden from List */
186      public boolean remove(Object object) {
187         throw uoroex();
188      }
189
190      @Override /* Overridden from List */
191      public boolean removeAll(Collection<?> c) {
192         throw uoroex();
193      }
194
195      @Override /* Overridden from Deque */
196      public Object removeFirst() {
197         throw uoroex();
198      }
199
200      @Override /* Overridden from Deque */
201      public boolean removeFirstOccurrence(Object object) {
202         throw uoroex();
203      }
204
205      @Override /* Overridden from Collection */
206      public boolean removeIf(Predicate<? super Object> filter) {
207         throw uoroex();
208      }
209
210      @Override /* Overridden from Deque */
211      public Object removeLast() {
212         throw uoroex();
213      }
214
215      @Override /* Overridden from Deque */
216      public boolean removeLastOccurrence(Object object) {
217         throw uoroex();
218      }
219
220      @Override /* Overridden from List */
221      public void replaceAll(UnaryOperator<Object> operator) {
222         throw uoroex();
223      }
224
225      @Override /* Overridden from List */
226      public boolean retainAll(Collection<?> c) {
227         throw uoroex();
228      }
229
230      @Override /* Overridden from List */
231      public Object set(int location, Object object) {
232         throw uoroex();
233      }
234
235      @Override /* Overridden from List */
236      public void sort(Comparator<? super Object> c) {
237         throw uoroex();
238      }
239
240      @Override /* Overridden from List */
241      public List<Object> subList(int start, int end) {
242         return u(new ArrayList<>(this).subList(start, end));
243      }
244
245      private static Iterator<Object> readOnlyIterator(Iterator<Object> it) {
246         return new Iterator<>() {
247            @Override public boolean hasNext() { return it.hasNext(); }
248            @Override public Object next() { return it.next(); }
249            @Override public void remove() { throw uoroex(); }
250            @Override public void forEachRemaining(Consumer<? super Object> action) { it.forEachRemaining(action); }
251         };
252      }
253
254      private static ListIterator<Object> readOnlyListIterator(ListIterator<Object> it) {
255         return new ListIterator<>() {
256            @Override public boolean hasNext() { return it.hasNext(); }
257            @Override public Object next() { return it.next(); }
258            @Override public boolean hasPrevious() { return it.hasPrevious(); }
259            @Override public Object previous() { return it.previous(); }
260            @Override public int nextIndex() { return it.nextIndex(); }
261            @Override public int previousIndex() { return it.previousIndex(); }
262            @Override public void remove() { throw uoroex(); }
263            @Override public void set(Object e) { throw uoroex(); }
264            @Override public void add(Object e) { throw uoroex(); }
265            @Override public void forEachRemaining(Consumer<? super Object> action) { it.forEachRemaining(action); }
266         };
267      }
268   }
269
270   private static final long serialVersionUID = 1L;
271
272   /**
273    * An empty read-only Json5List.
274    *
275    * @serial exclude
276    */
277   @SuppressWarnings({
278      "java:S2386" // Public static final field accessed externally, cannot be protected
279   })
280   public static final Json5List EMPTY_LIST = new Json5List() {
281      private static final long serialVersionUID = 1L;
282
283      @Override /* Overridden from List */
284      public void add(int location, Object object) {
285         throw uoroex();
286      }
287
288      @Override /* Overridden from List */
289      public ListIterator<Object> listIterator(int location) {
290         return Collections.emptyList().listIterator(location);
291      }
292
293      @Override /* Overridden from List */
294      public Object remove(int location) {
295         throw uoroex();
296      }
297
298      @Override /* Overridden from List */
299      public Object set(int location, Object object) {
300         throw uoroex();
301      }
302
303      @Override /* Overridden from List */
304      public List<Object> subList(int start, int end) {
305         return Collections.emptyList().subList(start, end);
306      }
307   };
308
309   /**
310    * Construct an empty list.
311    *
312    * @return An empty list.
313    */
314   public static Json5List create() {
315      return new Json5List();
316   }
317
318   /**
319    * Construct a list initialized with the specified list.
320    *
321    * @param values The list to copy. Can be <jk>null</jk> (returns <jk>null</jk>).
322    * @return A new list or <jk>null</jk> if the list was <jk>null</jk>.
323    */
324   public static Json5List of(Collection<?> values) {
325      return values == null ? null : new Json5List(values);
326   }
327
328   /**
329    * Construct a list initialized with the specified values.
330    *
331    * @param values The values to add to this list.
332    * @return A new list, never <jk>null</jk>.
333    */
334   public static Json5List of(Object...values) {
335      return new Json5List(values);
336   }
337
338   /**
339    * Convenience method for creating a list of array objects.
340    *
341    * @param values The initial values.
342    * @return A new list.
343    */
344   public static Json5List ofArrays(Object[]...values) {
345      var l = new Json5List();
346      Collections.addAll(l, values);
347      return l;
348   }
349
350   /**
351    * Convenience method for creating a list of collection objects.
352    *
353    * @param values The initial values.
354    * @return A new list.
355    */
356   public static Json5List ofCollections(Collection<?>...values) {
357      var l = new Json5List();
358      Collections.addAll(l, values);
359      return l;
360   }
361
362   /**
363    * Construct a list initialized with the specified JSON5 string.
364    *
365    * @param json5 The JSON5 text to parse.
366    * @return A new list (empty if the input was <jk>null</jk>), never <jk>null</jk>.
367    * @throws ParseException Malformed input encountered.
368    */
369   public static Json5List ofString(CharSequence json5) throws ParseException {
370      return json5 == null ? new Json5List() : new Json5List(json5);
371   }
372
373   /**
374    * Construct a list initialized with the specified reader containing JSON5.
375    *
376    * @param json5 The reader containing JSON5 text to parse.
377    * @return A new list (empty if the input was <jk>null</jk>), never <jk>null</jk>.
378    * @throws ParseException Malformed input encountered.
379    */
380   public static Json5List ofString(Reader json5) throws ParseException {
381      return json5 == null ? new Json5List() : new Json5List(json5);
382   }
383
384   /**
385    * Parses a string that can consist of either a JSON5 array or comma-delimited list.
386    *
387    * <p>
388    * The type of string is auto-detected.
389    *
390    * @param s The string to parse.
391    * @return A new list (empty if the input was <jk>null</jk> or empty), never <jk>null</jk>.
392    * @throws ParseException Malformed input encountered.
393    */
394   public static Json5List ofJson5OrCdl(String s) throws ParseException {
395      if (Shorts.ie(s))  // NOAI
396         return new Json5List();
397      if (! isProbablyJsonArray(s, true))
398         return new Json5List((Object[])splita(s.trim(), ','));
399      return new Json5List(s);
400   }
401
402   /**
403    * Construct a list initialized with the specified string.
404    *
405    * @param in The input being parsed.
406    * @param p The parser to use. If <jk>null</jk>, uses {@link Json5Parser}.
407    * @return A new list (empty if the input was <jk>null</jk>), never <jk>null</jk>.
408    * @throws ParseException Malformed input encountered.
409    */
410   public static Json5List ofString(CharSequence in, Parser p) throws ParseException {
411      return in == null ? new Json5List() : new Json5List(in, p);
412   }
413
414   /**
415    * Construct a list initialized with the specified reader.
416    *
417    * @param in The reader containing the input being parsed.
418    * @param p The parser to use. If <jk>null</jk>, uses {@link Json5Parser}.
419    * @return A new list (empty if the input was <jk>null</jk>), never <jk>null</jk>.
420    * @throws ParseException Malformed input encountered.
421    */
422   public static Json5List ofString(Reader in, Parser p) throws ParseException {
423      return in == null ? new Json5List() : new Json5List(in, p);
424   }
425
426   /**
427    * Construct an empty list.
428    */
429   public Json5List() {}
430
431   /**
432    * Construct an empty list with the specified bean context.
433    *
434    * @param session The bean session to use for creating beans.
435    */
436   public Json5List(MarshallingSession session) {
437      super(session);
438   }
439
440   /**
441    * Construct a list initialized with the specified JSON5 text.
442    *
443    * @param json5 The JSON5 text to parse.
444    * @throws ParseException Malformed input encountered.
445    */
446   public Json5List(CharSequence json5) throws ParseException {
447      this(json5, Json5Parser.DEFAULT);
448   }
449
450   /**
451    * Construct a list initialized with the specified string.
452    *
453    * @param in The input being parsed. Can be <jk>null</jk> or empty (results in an empty list; no parsing occurs).
454    * @param p The parser to use. If <jk>null</jk>, uses {@link Json5Parser}.
455    * @throws ParseException Malformed input encountered.
456    */
457   public Json5List(CharSequence in, Parser p) throws ParseException {
458      super(in, p == null ? Json5Parser.DEFAULT : p);
459   }
460
461   /**
462    * Construct a list initialized with the specified list.
463    *
464    * @param copyFrom The list to copy. Can be <jk>null</jk> (results in an empty list).
465    */
466   public Json5List(Collection<?> copyFrom) {
467      super(copyFrom);
468   }
469
470   /**
471    * Construct a list initialized with the contents.
472    *
473    * @param entries The entries to add to this list.
474    */
475   public Json5List(Object...entries) {
476      super(entries);
477   }
478
479   /**
480    * Construct a list initialized with the specified reader containing JSON5.
481    *
482    * @param json5 The reader containing JSON5 text to parse.
483    * @throws ParseException Malformed input encountered.
484    */
485   public Json5List(Reader json5) throws ParseException {
486      this(json5, Json5Parser.DEFAULT);
487   }
488
489   /**
490    * Construct a list initialized with the specified reader and parser.
491    *
492    * @param in The reader containing the input being parsed.
493    * @param p The parser to use. If <jk>null</jk>, uses {@link Json5Parser}.
494    * @throws ParseException Malformed input encountered.
495    */
496   public Json5List(Reader in, Parser p) throws ParseException {
497      super(in, p == null ? Json5Parser.DEFAULT : p);
498   }
499
500   @Override /* Overridden from MarshalledList */
501   public Json5List append(Collection<?> values) {
502      super.append(values);
503      return this;
504   }
505
506   @Override /* Overridden from MarshalledList */
507   public Json5List append(Object value) {
508      super.append(value);
509      return this;
510   }
511
512   @Override /* Overridden from MarshalledList */
513   public Json5List append(Object...values) {
514      super.append(values);
515      return this;
516   }
517
518   @Override /* Overridden from MarshalledList */
519   public Json5List appendIf(boolean flag, Object value) {
520      super.appendIf(flag, value);
521      return this;
522   }
523
524   @Override /* Overridden from MarshalledList */
525   public <T> Json5List appendIf(Predicate<T> test, T value) {
526      super.appendIf(test, value);
527      return this;
528   }
529
530   @Override /* Overridden from MarshalledList */
531   public Json5List appendReverse(List<?> values) {
532      super.appendReverse(values);
533      return this;
534   }
535
536   @Override /* Overridden from MarshalledList */
537   public Json5List appendReverse(Object...values) {
538      super.appendReverse(values);
539      return this;
540   }
541
542   /**
543    * Converts this object into the specified class type.
544    *
545    * <p>
546    * This method performs a round-trip serialization and deserialization to convert the list into the target type.
547    *
548    * <h5 class='section'>Notes:</h5><ul>
549    *    <li class='warn'>The current implementation uses a serialization round-trip which may be inefficient for
550    *       frequent conversions of large objects. Consider caching results or using direct object conversion where possible.
551    * </ul>
552    *
553    * @param cm The class type to convert this object to.
554    * @return A converted object.
555    */
556   public Object cast(ClassMeta<?> cm) {
557      try {
558         return Json5.to(Json5.of(this), cm);
559      } catch (ParseException | SerializeException e) {
560         throw toRex(e);
561      }
562   }
563
564   @Override /* Overridden from MarshalledList */
565   public Json5List getList(int index) {
566      return get(index, Json5List.class);
567   }
568
569   @Override /* Overridden from MarshalledList */
570   public Json5Map getMap(int index) {
571      return get(index, Json5Map.class);
572   }
573
574   @Override /* Overridden from MarshalledList */
575   public Json5List modifiable() {
576      if (isUnmodifiable())
577         return new Json5List(this);
578      return this;
579   }
580
581   @Override /* Overridden from MarshalledList */
582   public Json5List session(MarshallingSession session) {
583      super.session(session);
584      return this;
585   }
586
587   @Override /* Overridden from MarshalledList */
588   public Json5List setBeanSession(MarshallingSession value) {
589      super.setBeanSession(value);
590      return this;
591   }
592
593   /**
594    * Serializes this list to a JSON5 string.
595    *
596    * <p>
597    * A synonym for {@link #toString()}.
598    *
599    * @return This object as a JSON5 string.
600    */
601   public String toJson5() {
602      return Json5.of(this);
603   }
604
605   @Override /* Overridden from Object */
606   public String toString() {
607      return Json5.of(this);
608   }
609
610   @Override /* Overridden from MarshalledList */
611   public Json5List unmodifiable() {
612      if (this instanceof Unmodifiable this2)
613         return this2;
614      return new Unmodifiable(this);
615   }
616
617   /**
618    * Convenience method for serializing this list to the specified Writer using the {@link Json5#DEFAULT}
619    * marshaller.
620    *
621    * @param w The writer to send the serialized contents of this object.
622    * @return This object.
623    * @throws IOException If a problem occurred trying to write to the writer.
624    * @throws SerializeException If a problem occurred trying to convert the output.
625    */
626   public Json5List writeTo(Writer w) throws IOException, SerializeException {
627      Json5.DEFAULT.write(this, w);
628      return this;
629   }
630}