001/*
002 * Copyright 2015-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2017 UnboundID Corp.
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<JSONValue>(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 =
275         new ArrayList<JSONValue>(array.values);
276    final Iterator<JSONValue> thisIterator = values.iterator();
277    while (thisIterator.hasNext())
278    {
279      final JSONValue thisValue = thisIterator.next();
280
281      boolean found = false;
282      final Iterator<JSONValue> thatIterator = thatValues.iterator();
283      while (thatIterator.hasNext())
284      {
285        final JSONValue thatValue = thatIterator.next();
286        if (thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase,
287             ignoreArrayOrder))
288        {
289          found = true;
290          thatIterator.remove();
291          break;
292        }
293      }
294
295      if (! found)
296      {
297        return false;
298      }
299    }
300
301    return true;
302  }
303
304
305
306  /**
307   * {@inheritDoc}
308   */
309  @Override()
310  public boolean equals(final JSONValue v, final boolean ignoreFieldNameCase,
311                        final boolean ignoreValueCase,
312                        final boolean ignoreArrayOrder)
313  {
314    return ((v instanceof JSONArray) &&
315         equals((JSONArray) v, ignoreFieldNameCase, ignoreValueCase,
316              ignoreArrayOrder));
317  }
318
319
320
321  /**
322   * Indicates whether this JSON array contains an element with the specified
323   * value.
324   *
325   * @param  value                The value for which to make the determination.
326   * @param  ignoreFieldNameCase  Indicates whether to ignore differences in
327   *                              capitalization in field names for any JSON
328   *                              objects contained in the array.
329   * @param  ignoreValueCase      Indicates whether to ignore differences in
330   *                              capitalization for array elements that are
331   *                              JSON strings, as well as for the string values
332   *                              of any JSON objects and arrays contained in
333   *                              the array.
334   * @param  ignoreArrayOrder     Indicates whether to ignore differences in the
335   *                              order of elements contained in arrays.  This
336   *                              is only applicable if the provided value is
337   *                              itself an array or is a JSON object that
338   *                              contains values that are arrays.
339   * @param  recursive            Indicates whether to recursively look into any
340   *                              arrays contained inside this array.
341   *
342   * @return  {@code true} if this JSON array contains an element with the
343   *          specified value, or {@code false} if not.
344   */
345  public boolean contains(final JSONValue value,
346                          final boolean ignoreFieldNameCase,
347                          final boolean ignoreValueCase,
348                          final boolean ignoreArrayOrder,
349                          final boolean recursive)
350  {
351    for (final JSONValue v : values)
352    {
353      if (v.equals(value, ignoreFieldNameCase, ignoreValueCase,
354           ignoreArrayOrder))
355      {
356        return true;
357      }
358
359      if (recursive && (v instanceof JSONArray) &&
360          ((JSONArray) v).contains(value, ignoreFieldNameCase, ignoreValueCase,
361               ignoreArrayOrder, recursive))
362      {
363        return true;
364      }
365    }
366
367    return false;
368  }
369
370
371
372  /**
373   * Retrieves a string representation of this array as it should appear in a
374   * JSON object, including the surrounding square brackets.  Appropriate
375   * encoding will also be used for all elements in the array.    If the object
376   * containing this array was decoded from a string, then this method will use
377   * the same string representation as in that original object.  Otherwise, the
378   * string representation will be constructed.
379   *
380   * @return  A string representation of this array as it should appear in a
381   *          JSON object, including the surrounding square brackets.
382   */
383  @Override()
384  public String toString()
385  {
386    if (stringRepresentation == null)
387    {
388      final StringBuilder buffer = new StringBuilder();
389      toString(buffer);
390      stringRepresentation = buffer.toString();
391    }
392
393    return stringRepresentation;
394  }
395
396
397
398  /**
399   * Appends a string representation of this value as it should appear in a
400   * JSON object, including the surrounding square brackets,. to the provided
401   * buffer.  Appropriate encoding will also be used for all elements in the
402   * array.    If the object containing this array was decoded from a string,
403   * then this method will use the same string representation as in that
404   * original object.  Otherwise, the string representation will be constructed.
405   *
406   * @param  buffer  The buffer to which the information should be appended.
407   */
408  @Override()
409  public void toString(final StringBuilder buffer)
410  {
411    if (stringRepresentation != null)
412    {
413      buffer.append(stringRepresentation);
414      return;
415    }
416
417    buffer.append("[ ");
418
419    final Iterator<JSONValue> iterator = values.iterator();
420    while (iterator.hasNext())
421    {
422      iterator.next().toString(buffer);
423      if (iterator.hasNext())
424      {
425        buffer.append(',');
426      }
427      buffer.append(' ');
428    }
429
430    buffer.append(']');
431  }
432
433
434
435  /**
436   * Retrieves a single-line string representation of this array as it should
437   * appear in a JSON object, including the surrounding square brackets.
438   * Appropriate encoding will also be used for all elements in the array.
439   *
440   * @return  A string representation of this array as it should appear in a
441   *          JSON object, including the surrounding square brackets.
442   */
443  @Override()
444  public String toSingleLineString()
445  {
446    final StringBuilder buffer = new StringBuilder();
447    toSingleLineString(buffer);
448    return buffer.toString();
449  }
450
451
452
453  /**
454   * Appends a single-line string representation of this array as it should
455   * appear in a JSON object, including the surrounding square brackets, to the
456   * provided buffer.  Appropriate encoding will also be used for all elements
457   * in the array.
458   *
459   * @param  buffer  The buffer to which the information should be appended.
460   */
461  @Override()
462  public void toSingleLineString(final StringBuilder buffer)
463  {
464    buffer.append("[ ");
465
466    final Iterator<JSONValue> iterator = values.iterator();
467    while (iterator.hasNext())
468    {
469      iterator.next().toSingleLineString(buffer);
470      if (iterator.hasNext())
471      {
472        buffer.append(',');
473      }
474      buffer.append(' ');
475    }
476
477    buffer.append(']');
478  }
479
480
481
482  /**
483   * Retrieves a normalized string representation of this array.  The normalized
484   * representation will not contain any line breaks, will not include any
485   * spaces around the enclosing brackets or around commas used to separate the
486   * elements, and it will use the normalized representations of those elements.
487   * The order of elements in an array is considered significant, and will not
488   * be affected by the normalization process.
489   *
490   * @return  A normalized string representation of this array.
491   */
492  @Override()
493  public String toNormalizedString()
494  {
495    final StringBuilder buffer = new StringBuilder();
496    toNormalizedString(buffer);
497    return buffer.toString();
498  }
499
500
501
502  /**
503   * Appends a normalized string representation of this array to the provided
504   * buffer.  The normalized representation will not contain any line breaks,
505   * will not include any spaces around the enclosing brackets or around commas
506   * used to separate the elements, and it will use the normalized
507   * representations of those elements. The order of elements in an array is
508   * considered significant, and will not be affected by the normalization
509   * process.
510   *
511   * @param  buffer  The buffer to which the information should be appended.
512   */
513  @Override()
514  public void toNormalizedString(final StringBuilder buffer)
515  {
516    buffer.append('[');
517
518    final Iterator<JSONValue> iterator = values.iterator();
519    while (iterator.hasNext())
520    {
521      iterator.next().toNormalizedString(buffer);
522      if (iterator.hasNext())
523      {
524        buffer.append(',');
525      }
526    }
527
528    buffer.append(']');
529  }
530
531
532
533  /**
534   * {@inheritDoc}
535   */
536  @Override()
537  public void appendToJSONBuffer(final JSONBuffer buffer)
538  {
539    buffer.beginArray();
540
541    for (final JSONValue value : values)
542    {
543      value.appendToJSONBuffer(buffer);
544    }
545
546    buffer.endArray();
547  }
548
549
550
551  /**
552   * {@inheritDoc}
553   */
554  @Override()
555  public void appendToJSONBuffer(final String fieldName,
556                                 final JSONBuffer buffer)
557  {
558    buffer.beginArray(fieldName);
559
560    for (final JSONValue value : values)
561    {
562      value.appendToJSONBuffer(buffer);
563    }
564
565    buffer.endArray();
566  }
567}