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     */
021    package com.unboundid.util.json;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Arrays;
027    import java.util.Collections;
028    import java.util.Iterator;
029    import java.util.List;
030    
031    import com.unboundid.util.NotMutable;
032    import com.unboundid.util.ThreadSafety;
033    import 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)
065    public 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    }