001 /*
002 * Copyright 2015-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2015-2016 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 * {@inheritDoc}
374 */
375 @Override()
376 public String toString()
377 {
378 if (stringRepresentation == null)
379 {
380 final StringBuilder buffer = new StringBuilder();
381 toString(buffer);
382 stringRepresentation = buffer.toString();
383 }
384
385 return stringRepresentation;
386 }
387
388
389
390 /**
391 * {@inheritDoc}
392 */
393 @Override()
394 public void toString(final StringBuilder buffer)
395 {
396 if (stringRepresentation != null)
397 {
398 buffer.append(stringRepresentation);
399 return;
400 }
401
402 buffer.append("[ ");
403
404 final Iterator<JSONValue> iterator = values.iterator();
405 while (iterator.hasNext())
406 {
407 iterator.next().toString(buffer);
408 if (iterator.hasNext())
409 {
410 buffer.append(',');
411 }
412 buffer.append(' ');
413 }
414
415 buffer.append(']');
416 }
417
418
419
420 /**
421 * {@inheritDoc}
422 */
423 @Override()
424 public String toSingleLineString()
425 {
426 final StringBuilder buffer = new StringBuilder();
427 toSingleLineString(buffer);
428 return buffer.toString();
429 }
430
431
432
433 /**
434 * {@inheritDoc}
435 */
436 @Override()
437 public void toSingleLineString(final StringBuilder buffer)
438 {
439 buffer.append("[ ");
440
441 final Iterator<JSONValue> iterator = values.iterator();
442 while (iterator.hasNext())
443 {
444 iterator.next().toSingleLineString(buffer);
445 if (iterator.hasNext())
446 {
447 buffer.append(',');
448 }
449 buffer.append(' ');
450 }
451
452 buffer.append(']');
453 }
454
455
456
457 /**
458 * {@inheritDoc}
459 */
460 @Override()
461 public String toNormalizedString()
462 {
463 final StringBuilder buffer = new StringBuilder();
464 toNormalizedString(buffer);
465 return buffer.toString();
466 }
467
468
469
470 /**
471 * {@inheritDoc}
472 */
473 @Override()
474 public void toNormalizedString(final StringBuilder buffer)
475 {
476 buffer.append('[');
477
478 final Iterator<JSONValue> iterator = values.iterator();
479 while (iterator.hasNext())
480 {
481 iterator.next().toNormalizedString(buffer);
482 if (iterator.hasNext())
483 {
484 buffer.append(',');
485 }
486 }
487
488 buffer.append(']');
489 }
490
491
492
493 /**
494 * {@inheritDoc}
495 */
496 @Override()
497 public void appendToJSONBuffer(final JSONBuffer buffer)
498 {
499 buffer.beginArray();
500
501 for (final JSONValue value : values)
502 {
503 value.appendToJSONBuffer(buffer);
504 }
505
506 buffer.endArray();
507 }
508
509
510
511 /**
512 * {@inheritDoc}
513 */
514 @Override()
515 public void appendToJSONBuffer(final String fieldName,
516 final JSONBuffer buffer)
517 {
518 buffer.beginArray(fieldName);
519
520 for (final JSONValue value : values)
521 {
522 value.appendToJSONBuffer(buffer);
523 }
524
525 buffer.endArray();
526 }
527 }