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}