001/*
002 * Copyright 2015-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util.json;
022
023
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.Iterator;
029import java.util.List;
030
031import com.unboundid.util.NotMutable;
032import com.unboundid.util.ThreadSafety;
033import com.unboundid.util.ThreadSafetyLevel;
034
035
036
037/**
038 * This class provides an implementation of a JSON value that represents an
039 * ordered collection of zero or more values.  An array can contain elements of
040 * any type, including a mix of types, and including nested arrays.  The same
041 * value may appear multiple times in an array.
042 * <BR><BR>
043 * The string representation of a JSON array is an open square bracket (U+005B)
044 * followed by a comma-delimited list of the string representations of the
045 * values in that array and a closing square bracket (U+005D).  There must not
046 * be a comma between the last item in the array and the closing square bracket.
047 * There may optionally be any amount of whitespace (where whitespace characters
048 * include the ASCII space, horizontal tab, line feed, and carriage return
049 * characters) after the open square bracket, on either or both sides of commas
050 * separating values, and before the close square bracket.
051 * <BR><BR>
052 * The string representation returned by the {@link #toString()} method (or
053 * appended to the buffer provided to the {@link #toString(StringBuilder)}
054 * method) will include one space before each value in the array and one space
055 * before the closing square bracket.  There will not be any space between a
056 * value and the comma that follows it.  The string representation of each value
057 * in the array will be obtained using that value's {@code toString} method.
058 * <BR><BR>
059 * The normalized string representation will not include any optional spaces,
060 * and the normalized string representation of each value in the array will be
061 * obtained using that value's {@code toNormalizedString} method.
062 */
063@NotMutable()
064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
065public final class JSONArray
066       extends JSONValue
067{
068  /**
069   * A pre-allocated empty JSON array.
070   */
071  public static final JSONArray EMPTY_ARRAY = new JSONArray();
072
073
074
075  /**
076   * The serial version UID for this serializable class.
077   */
078  private static final long serialVersionUID = -5493008945333225318L;
079
080
081
082  // The hash code for this JSON array.
083  private Integer hashCode;
084
085  // The list of values for this array.
086  private final List<JSONValue> values;
087
088  // The string representation for this JSON array.
089  private String stringRepresentation;
090
091
092
093  /**
094   * Creates a new JSON array with the provided values.
095   *
096   * @param  values  The set of values to include in this JSON array.  It may be
097   *                 {@code null} or empty to indicate that the array should be
098   *                 empty.
099   */
100  public JSONArray(final JSONValue... values)
101  {
102    this((values == null) ? null : Arrays.asList(values));
103  }
104
105
106
107  /**
108   * Creates a new JSON array with the provided values.
109   *
110   * @param  values  The set of values to include in this JSON array.  It may be
111   *                 {@code null} or empty to indicate that the array should be
112   *                 empty.
113   */
114  public JSONArray(final List<? extends JSONValue> values)
115  {
116    if (values == null)
117    {
118      this.values = Collections.emptyList();
119    }
120    else
121    {
122      this.values =
123           Collections.unmodifiableList(new ArrayList<>(values));
124    }
125
126    hashCode = null;
127    stringRepresentation = null;
128  }
129
130
131
132  /**
133   * Retrieves the set of values contained in this JSON array.
134   *
135   * @return  The set of values contained in this JSON array.
136   */
137  public List<JSONValue> getValues()
138  {
139    return values;
140  }
141
142
143
144  /**
145   * Indicates whether this array is empty.
146   *
147   * @return  {@code true} if this array does not contain any values, or
148   *          {@code false} if this array contains at least one value.
149   */
150  public boolean isEmpty()
151  {
152    return values.isEmpty();
153  }
154
155
156
157  /**
158   * Retrieves the number of values contained in this array.
159   *
160   * @return  The number of values contained in this array.
161   */
162  public int size()
163  {
164    return values.size();
165  }
166
167
168
169  /**
170   * {@inheritDoc}
171   */
172  @Override()
173  public int hashCode()
174  {
175    if (hashCode == null)
176    {
177      int hc = 0;
178      for (final JSONValue v : values)
179      {
180        hc = (hc * 31) + v.hashCode();
181      }
182
183      hashCode = hc;
184    }
185
186    return hashCode;
187  }
188
189
190
191  /**
192   * {@inheritDoc}
193   */
194  @Override()
195  public boolean equals(final Object o)
196  {
197    if (o == this)
198    {
199      return true;
200    }
201
202    if (o instanceof JSONArray)
203    {
204      final JSONArray a = (JSONArray) o;
205      return values.equals(a.values);
206    }
207
208    return false;
209  }
210
211
212
213  /**
214   * Indicates whether this JSON array is considered equivalent to the provided
215   * array, subject to the specified constraints.
216   *
217   * @param  array                The array for which to make the determination.
218   * @param  ignoreFieldNameCase  Indicates whether to ignore differences in
219   *                              capitalization in field names for any JSON
220   *                              objects contained in the array.
221   * @param  ignoreValueCase      Indicates whether to ignore differences in
222   *                              capitalization for array elements that are
223   *                              JSON strings, as well as for the string values
224   *                              of any JSON objects and arrays contained in
225   *                              the array.
226   * @param  ignoreArrayOrder     Indicates whether to ignore differences in the
227   *                              order of elements contained in the array.
228   *
229   * @return  {@code true} if this JSON array is considered equivalent to the
230   *          provided array (subject to the specified constraints), or
231   *          {@code false} if not.
232   */
233  public boolean equals(final JSONArray array,
234                        final boolean ignoreFieldNameCase,
235                        final boolean ignoreValueCase,
236                        final boolean ignoreArrayOrder)
237  {
238    // See if we can do a straight-up List.equals.  If so, just do that.
239    if ((! ignoreFieldNameCase) && (! ignoreValueCase) && (! ignoreArrayOrder))
240    {
241      return values.equals(array.values);
242    }
243
244    // Make sure the arrays have the same number of elements.
245    if (values.size() != array.values.size())
246    {
247      return false;
248    }
249
250    // Optimize for the case in which the order of values is significant.
251    if (! ignoreArrayOrder)
252    {
253      final Iterator<JSONValue> thisIterator = values.iterator();
254      final Iterator<JSONValue> thatIterator = array.values.iterator();
255      while (thisIterator.hasNext())
256      {
257        final JSONValue thisValue = thisIterator.next();
258        final JSONValue thatValue = thatIterator.next();
259        if (! thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase,
260             ignoreArrayOrder))
261        {
262          return false;
263        }
264      }
265
266      return true;
267    }
268
269
270    // If we've gotten here, then we know that we don't care about the order.
271    // Create a new list that we can remove values from as we find matches.
272    // This is important because arrays can have duplicate values, and we don't
273    // want to keep matching the same element.
274    final ArrayList<JSONValue> thatValues = new ArrayList<>(array.values);
275    final Iterator<JSONValue> thisIterator = values.iterator();
276    while (thisIterator.hasNext())
277    {
278      final JSONValue thisValue = thisIterator.next();
279
280      boolean found = false;
281      final Iterator<JSONValue> thatIterator = thatValues.iterator();
282      while (thatIterator.hasNext())
283      {
284        final JSONValue thatValue = thatIterator.next();
285        if (thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase,
286             ignoreArrayOrder))
287        {
288          found = true;
289          thatIterator.remove();
290          break;
291        }
292      }
293
294      if (! found)
295      {
296        return false;
297      }
298    }
299
300    return true;
301  }
302
303
304
305  /**
306   * {@inheritDoc}
307   */
308  @Override()
309  public boolean equals(final JSONValue v, final boolean ignoreFieldNameCase,
310                        final boolean ignoreValueCase,
311                        final boolean ignoreArrayOrder)
312  {
313    return ((v instanceof JSONArray) &&
314         equals((JSONArray) v, ignoreFieldNameCase, ignoreValueCase,
315              ignoreArrayOrder));
316  }
317
318
319
320  /**
321   * Indicates whether this JSON array contains an element with the specified
322   * value.
323   *
324   * @param  value                The value for which to make the determination.
325   * @param  ignoreFieldNameCase  Indicates whether to ignore differences in
326   *                              capitalization in field names for any JSON
327   *                              objects contained in the array.
328   * @param  ignoreValueCase      Indicates whether to ignore differences in
329   *                              capitalization for array elements that are
330   *                              JSON strings, as well as for the string values
331   *                              of any JSON objects and arrays contained in
332   *                              the array.
333   * @param  ignoreArrayOrder     Indicates whether to ignore differences in the
334   *                              order of elements contained in arrays.  This
335   *                              is only applicable if the provided value is
336   *                              itself an array or is a JSON object that
337   *                              contains values that are arrays.
338   * @param  recursive            Indicates whether to recursively look into any
339   *                              arrays contained inside this array.
340   *
341   * @return  {@code true} if this JSON array contains an element with the
342   *          specified value, or {@code false} if not.
343   */
344  public boolean contains(final JSONValue value,
345                          final boolean ignoreFieldNameCase,
346                          final boolean ignoreValueCase,
347                          final boolean ignoreArrayOrder,
348                          final boolean recursive)
349  {
350    for (final JSONValue v : values)
351    {
352      if (v.equals(value, ignoreFieldNameCase, ignoreValueCase,
353           ignoreArrayOrder))
354      {
355        return true;
356      }
357
358      if (recursive && (v instanceof JSONArray) &&
359          ((JSONArray) v).contains(value, ignoreFieldNameCase, ignoreValueCase,
360               ignoreArrayOrder, recursive))
361      {
362        return true;
363      }
364    }
365
366    return false;
367  }
368
369
370
371  /**
372   * Retrieves a string representation of this array as it should appear in a
373   * JSON object, including the surrounding square brackets.  Appropriate
374   * encoding will also be used for all elements in the array.    If the object
375   * containing this array was decoded from a string, then this method will use
376   * the same string representation as in that original object.  Otherwise, the
377   * string representation will be constructed.
378   *
379   * @return  A string representation of this array as it should appear in a
380   *          JSON object, including the surrounding square brackets.
381   */
382  @Override()
383  public String toString()
384  {
385    if (stringRepresentation == null)
386    {
387      final StringBuilder buffer = new StringBuilder();
388      toString(buffer);
389      stringRepresentation = buffer.toString();
390    }
391
392    return stringRepresentation;
393  }
394
395
396
397  /**
398   * Appends a string representation of this value as it should appear in a
399   * JSON object, including the surrounding square brackets,. to the provided
400   * buffer.  Appropriate encoding will also be used for all elements in the
401   * array.    If the object containing this array was decoded from a string,
402   * then this method will use the same string representation as in that
403   * original object.  Otherwise, the string representation will be constructed.
404   *
405   * @param  buffer  The buffer to which the information should be appended.
406   */
407  @Override()
408  public void toString(final StringBuilder buffer)
409  {
410    if (stringRepresentation != null)
411    {
412      buffer.append(stringRepresentation);
413      return;
414    }
415
416    buffer.append("[ ");
417
418    final Iterator<JSONValue> iterator = values.iterator();
419    while (iterator.hasNext())
420    {
421      iterator.next().toString(buffer);
422      if (iterator.hasNext())
423      {
424        buffer.append(',');
425      }
426      buffer.append(' ');
427    }
428
429    buffer.append(']');
430  }
431
432
433
434  /**
435   * Retrieves a single-line string representation of this array as it should
436   * appear in a JSON object, including the surrounding square brackets.
437   * Appropriate encoding will also be used for all elements in the array.
438   *
439   * @return  A string representation of this array as it should appear in a
440   *          JSON object, including the surrounding square brackets.
441   */
442  @Override()
443  public String toSingleLineString()
444  {
445    final StringBuilder buffer = new StringBuilder();
446    toSingleLineString(buffer);
447    return buffer.toString();
448  }
449
450
451
452  /**
453   * Appends a single-line string representation of this array as it should
454   * appear in a JSON object, including the surrounding square brackets, to the
455   * provided buffer.  Appropriate encoding will also be used for all elements
456   * in the array.
457   *
458   * @param  buffer  The buffer to which the information should be appended.
459   */
460  @Override()
461  public void toSingleLineString(final StringBuilder buffer)
462  {
463    buffer.append("[ ");
464
465    final Iterator<JSONValue> iterator = values.iterator();
466    while (iterator.hasNext())
467    {
468      iterator.next().toSingleLineString(buffer);
469      if (iterator.hasNext())
470      {
471        buffer.append(',');
472      }
473      buffer.append(' ');
474    }
475
476    buffer.append(']');
477  }
478
479
480
481  /**
482   * Retrieves a normalized string representation of this array.  The normalized
483   * representation will not contain any line breaks, will not include any
484   * spaces around the enclosing brackets or around commas used to separate the
485   * elements, and it will use the normalized representations of those elements.
486   * The order of elements in an array is considered significant, and will not
487   * be affected by the normalization process.
488   *
489   * @return  A normalized string representation of this array.
490   */
491  @Override()
492  public String toNormalizedString()
493  {
494    final StringBuilder buffer = new StringBuilder();
495    toNormalizedString(buffer);
496    return buffer.toString();
497  }
498
499
500
501  /**
502   * Appends a normalized string representation of this array to the provided
503   * buffer.  The normalized representation will not contain any line breaks,
504   * will not include any spaces around the enclosing brackets or around commas
505   * used to separate the elements, and it will use the normalized
506   * representations of those elements. The order of elements in an array is
507   * considered significant, and will not be affected by the normalization
508   * process.
509   *
510   * @param  buffer  The buffer to which the information should be appended.
511   */
512  @Override()
513  public void toNormalizedString(final StringBuilder buffer)
514  {
515    toNormalizedString(buffer, false, true, false);
516  }
517
518
519
520  /**
521   * Retrieves a normalized string representation of this array.  The normalized
522   * representation will not contain any line breaks, will not include any
523   * spaces around the enclosing brackets or around commas used to separate the
524   * elements, and it will use the normalized representations of those elements.
525   * The order of elements in an array is considered significant, and will not
526   * be affected by the normalization process.
527   *
528   * @param  ignoreFieldNameCase  Indicates whether field names should be
529   *                              treated in a case-sensitive (if {@code false})
530   *                              or case-insensitive (if {@code true}) manner.
531   * @param  ignoreValueCase      Indicates whether string field values should
532   *                              be treated in a case-sensitive (if
533   *                              {@code false}) or case-insensitive (if
534   *                              {@code true}) manner.
535   * @param  ignoreArrayOrder     Indicates whether the order of elements in an
536   *                              array should be considered significant (if
537   *                              {@code false}) or insignificant (if
538   *                              {@code true}).
539   *
540   * @return  A normalized string representation of this array.
541   */
542  @Override()
543  public String toNormalizedString(final boolean ignoreFieldNameCase,
544                                   final boolean ignoreValueCase,
545                                   final boolean ignoreArrayOrder)
546  {
547    final StringBuilder buffer = new StringBuilder();
548    toNormalizedString(buffer, ignoreFieldNameCase, ignoreValueCase,
549         ignoreArrayOrder);
550    return buffer.toString();
551  }
552
553
554
555  /**
556   * Appends a normalized string representation of this array to the provided
557   * buffer.  The normalized representation will not contain any line breaks,
558   * will not include any spaces around the enclosing brackets or around commas
559   * used to separate the elements, and it will use the normalized
560   * representations of those elements. The order of elements in an array is
561   * considered significant, and will not be affected by the normalization
562   * process.
563   *
564   * @param  buffer               The buffer to which the information should be
565   *                              appended.
566   * @param  ignoreFieldNameCase  Indicates whether field names should be
567   *                              treated in a case-sensitive (if {@code false})
568   *                              or case-insensitive (if {@code true}) manner.
569   * @param  ignoreValueCase      Indicates whether string field values should
570   *                              be treated in a case-sensitive (if
571   *                              {@code false}) or case-insensitive (if
572   *                              {@code true}) manner.
573   * @param  ignoreArrayOrder     Indicates whether the order of elements in an
574   *                              array should be considered significant (if
575   *                              {@code false}) or insignificant (if
576   *                              {@code true}).
577   */
578  @Override()
579  public void toNormalizedString(final StringBuilder buffer,
580                                 final boolean ignoreFieldNameCase,
581                                 final boolean ignoreValueCase,
582                                 final boolean ignoreArrayOrder)
583  {
584    final List<String> normalizedValues = new ArrayList<>(values.size());
585    for (final JSONValue v : values)
586    {
587      normalizedValues.add(v.toNormalizedString(ignoreFieldNameCase,
588           ignoreValueCase, ignoreArrayOrder));
589    }
590
591    if (ignoreArrayOrder)
592    {
593      Collections.sort(normalizedValues);
594    }
595
596    buffer.append('[');
597
598    final Iterator<String> iterator = normalizedValues.iterator();
599    while (iterator.hasNext())
600    {
601      buffer.append(iterator.next());
602      if (iterator.hasNext())
603      {
604        buffer.append(',');
605      }
606    }
607
608    buffer.append(']');
609  }
610
611
612
613  /**
614   * {@inheritDoc}
615   */
616  @Override()
617  public void appendToJSONBuffer(final JSONBuffer buffer)
618  {
619    buffer.beginArray();
620
621    for (final JSONValue value : values)
622    {
623      value.appendToJSONBuffer(buffer);
624    }
625
626    buffer.endArray();
627  }
628
629
630
631  /**
632   * {@inheritDoc}
633   */
634  @Override()
635  public void appendToJSONBuffer(final String fieldName,
636                                 final JSONBuffer buffer)
637  {
638    buffer.beginArray(fieldName);
639
640    for (final JSONValue value : values)
641    {
642      value.appendToJSONBuffer(buffer);
643    }
644
645    buffer.endArray();
646  }
647}