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