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 }