001/* 002 * Copyright 2015-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2015-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2015-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.util.json; 037 038 039 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.Collections; 043import java.util.Iterator; 044import java.util.List; 045 046import com.unboundid.util.NotMutable; 047import com.unboundid.util.ThreadSafety; 048import com.unboundid.util.ThreadSafetyLevel; 049 050 051 052/** 053 * This class provides an implementation of a JSON value that represents an 054 * ordered collection of zero or more values. An array can contain elements of 055 * any type, including a mix of types, and including nested arrays. The same 056 * value may appear multiple times in an array. 057 * <BR><BR> 058 * The string representation of a JSON array is an open square bracket (U+005B) 059 * followed by a comma-delimited list of the string representations of the 060 * values in that array and a closing square bracket (U+005D). There must not 061 * be a comma between the last item in the array and the closing square bracket. 062 * There may optionally be any amount of whitespace (where whitespace characters 063 * include the ASCII space, horizontal tab, line feed, and carriage return 064 * characters) after the open square bracket, on either or both sides of commas 065 * separating values, and before the close square bracket. 066 * <BR><BR> 067 * The string representation returned by the {@link #toString()} method (or 068 * appended to the buffer provided to the {@link #toString(StringBuilder)} 069 * method) will include one space before each value in the array and one space 070 * before the closing square bracket. There will not be any space between a 071 * value and the comma that follows it. The string representation of each value 072 * in the array will be obtained using that value's {@code toString} method. 073 * <BR><BR> 074 * The normalized string representation will not include any optional spaces, 075 * and the normalized string representation of each value in the array will be 076 * obtained using that value's {@code toNormalizedString} method. 077 */ 078@NotMutable() 079@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 080public final class JSONArray 081 extends JSONValue 082{ 083 /** 084 * A pre-allocated empty JSON array. 085 */ 086 public static final JSONArray EMPTY_ARRAY = new JSONArray(); 087 088 089 090 /** 091 * The serial version UID for this serializable class. 092 */ 093 private static final long serialVersionUID = -5493008945333225318L; 094 095 096 097 // The hash code for this JSON array. 098 private Integer hashCode; 099 100 // The list of values for this array. 101 private final List<JSONValue> values; 102 103 // The string representation for this JSON array. 104 private String stringRepresentation; 105 106 107 108 /** 109 * Creates a new JSON array with the provided values. 110 * 111 * @param values The set of values to include in this JSON array. It may be 112 * {@code null} or empty to indicate that the array should be 113 * empty. 114 */ 115 public JSONArray(final JSONValue... values) 116 { 117 this((values == null) ? null : Arrays.asList(values)); 118 } 119 120 121 122 /** 123 * Creates a new JSON array with the provided values. 124 * 125 * @param values The set of values to include in this JSON array. It may be 126 * {@code null} or empty to indicate that the array should be 127 * empty. 128 */ 129 public JSONArray(final List<? extends JSONValue> values) 130 { 131 if (values == null) 132 { 133 this.values = Collections.emptyList(); 134 } 135 else 136 { 137 this.values = 138 Collections.unmodifiableList(new ArrayList<>(values)); 139 } 140 141 hashCode = null; 142 stringRepresentation = null; 143 } 144 145 146 147 /** 148 * Retrieves the set of values contained in this JSON array. 149 * 150 * @return The set of values contained in this JSON array. 151 */ 152 public List<JSONValue> getValues() 153 { 154 return values; 155 } 156 157 158 159 /** 160 * Indicates whether this array is empty. 161 * 162 * @return {@code true} if this array does not contain any values, or 163 * {@code false} if this array contains at least one value. 164 */ 165 public boolean isEmpty() 166 { 167 return values.isEmpty(); 168 } 169 170 171 172 /** 173 * Retrieves the number of values contained in this array. 174 * 175 * @return The number of values contained in this array. 176 */ 177 public int size() 178 { 179 return values.size(); 180 } 181 182 183 184 /** 185 * {@inheritDoc} 186 */ 187 @Override() 188 public int hashCode() 189 { 190 if (hashCode == null) 191 { 192 int hc = 0; 193 for (final JSONValue v : values) 194 { 195 hc = (hc * 31) + v.hashCode(); 196 } 197 198 hashCode = hc; 199 } 200 201 return hashCode; 202 } 203 204 205 206 /** 207 * {@inheritDoc} 208 */ 209 @Override() 210 public boolean equals(final Object o) 211 { 212 if (o == this) 213 { 214 return true; 215 } 216 217 if (o instanceof JSONArray) 218 { 219 final JSONArray a = (JSONArray) o; 220 return values.equals(a.values); 221 } 222 223 return false; 224 } 225 226 227 228 /** 229 * Indicates whether this JSON array is considered equivalent to the provided 230 * array, subject to the specified constraints. 231 * 232 * @param array The array for which to make the determination. 233 * @param ignoreFieldNameCase Indicates whether to ignore differences in 234 * capitalization in field names for any JSON 235 * objects contained in the array. 236 * @param ignoreValueCase Indicates whether to ignore differences in 237 * capitalization for array elements that are 238 * JSON strings, as well as for the string values 239 * of any JSON objects and arrays contained in 240 * the array. 241 * @param ignoreArrayOrder Indicates whether to ignore differences in the 242 * order of elements contained in the array. 243 * 244 * @return {@code true} if this JSON array is considered equivalent to the 245 * provided array (subject to the specified constraints), or 246 * {@code false} if not. 247 */ 248 public boolean equals(final JSONArray array, 249 final boolean ignoreFieldNameCase, 250 final boolean ignoreValueCase, 251 final boolean ignoreArrayOrder) 252 { 253 // See if we can do a straight-up List.equals. If so, just do that. 254 if ((! ignoreFieldNameCase) && (! ignoreValueCase) && (! ignoreArrayOrder)) 255 { 256 return values.equals(array.values); 257 } 258 259 // Make sure the arrays have the same number of elements. 260 if (values.size() != array.values.size()) 261 { 262 return false; 263 } 264 265 // Optimize for the case in which the order of values is significant. 266 if (! ignoreArrayOrder) 267 { 268 final Iterator<JSONValue> thisIterator = values.iterator(); 269 final Iterator<JSONValue> thatIterator = array.values.iterator(); 270 while (thisIterator.hasNext()) 271 { 272 final JSONValue thisValue = thisIterator.next(); 273 final JSONValue thatValue = thatIterator.next(); 274 if (! thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase, 275 ignoreArrayOrder)) 276 { 277 return false; 278 } 279 } 280 281 return true; 282 } 283 284 285 // If we've gotten here, then we know that we don't care about the order. 286 // Create a new list that we can remove values from as we find matches. 287 // This is important because arrays can have duplicate values, and we don't 288 // want to keep matching the same element. 289 final ArrayList<JSONValue> thatValues = new ArrayList<>(array.values); 290 final Iterator<JSONValue> thisIterator = values.iterator(); 291 while (thisIterator.hasNext()) 292 { 293 final JSONValue thisValue = thisIterator.next(); 294 295 boolean found = false; 296 final Iterator<JSONValue> thatIterator = thatValues.iterator(); 297 while (thatIterator.hasNext()) 298 { 299 final JSONValue thatValue = thatIterator.next(); 300 if (thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase, 301 ignoreArrayOrder)) 302 { 303 found = true; 304 thatIterator.remove(); 305 break; 306 } 307 } 308 309 if (! found) 310 { 311 return false; 312 } 313 } 314 315 return true; 316 } 317 318 319 320 /** 321 * {@inheritDoc} 322 */ 323 @Override() 324 public boolean equals(final JSONValue v, final boolean ignoreFieldNameCase, 325 final boolean ignoreValueCase, 326 final boolean ignoreArrayOrder) 327 { 328 return ((v instanceof JSONArray) && 329 equals((JSONArray) v, ignoreFieldNameCase, ignoreValueCase, 330 ignoreArrayOrder)); 331 } 332 333 334 335 /** 336 * Indicates whether this JSON array contains an element with the specified 337 * value. 338 * 339 * @param value The value for which to make the determination. 340 * @param ignoreFieldNameCase Indicates whether to ignore differences in 341 * capitalization in field names for any JSON 342 * objects contained in the array. 343 * @param ignoreValueCase Indicates whether to ignore differences in 344 * capitalization for array elements that are 345 * JSON strings, as well as for the string values 346 * of any JSON objects and arrays contained in 347 * the array. 348 * @param ignoreArrayOrder Indicates whether to ignore differences in the 349 * order of elements contained in arrays. This 350 * is only applicable if the provided value is 351 * itself an array or is a JSON object that 352 * contains values that are arrays. 353 * @param recursive Indicates whether to recursively look into any 354 * arrays contained inside this array. 355 * 356 * @return {@code true} if this JSON array contains an element with the 357 * specified value, or {@code false} if not. 358 */ 359 public boolean contains(final JSONValue value, 360 final boolean ignoreFieldNameCase, 361 final boolean ignoreValueCase, 362 final boolean ignoreArrayOrder, 363 final boolean recursive) 364 { 365 for (final JSONValue v : values) 366 { 367 if (v.equals(value, ignoreFieldNameCase, ignoreValueCase, 368 ignoreArrayOrder)) 369 { 370 return true; 371 } 372 373 if (recursive && (v instanceof JSONArray) && 374 ((JSONArray) v).contains(value, ignoreFieldNameCase, ignoreValueCase, 375 ignoreArrayOrder, recursive)) 376 { 377 return true; 378 } 379 } 380 381 return false; 382 } 383 384 385 386 /** 387 * Retrieves a string representation of this array as it should appear in a 388 * JSON object, including the surrounding square brackets. Appropriate 389 * encoding will also be used for all elements in the array. If the object 390 * containing this array was decoded from a string, then this method will use 391 * the same string representation as in that original object. Otherwise, the 392 * string representation will be constructed. 393 * 394 * @return A string representation of this array as it should appear in a 395 * JSON object, including the surrounding square brackets. 396 */ 397 @Override() 398 public String toString() 399 { 400 if (stringRepresentation == null) 401 { 402 final StringBuilder buffer = new StringBuilder(); 403 toString(buffer); 404 stringRepresentation = buffer.toString(); 405 } 406 407 return stringRepresentation; 408 } 409 410 411 412 /** 413 * Appends a string representation of this value as it should appear in a 414 * JSON object, including the surrounding square brackets,. to the provided 415 * buffer. Appropriate encoding will also be used for all elements in the 416 * array. If the object containing this array was decoded from a string, 417 * then this method will use the same string representation as in that 418 * original object. Otherwise, the string representation will be constructed. 419 * 420 * @param buffer The buffer to which the information should be appended. 421 */ 422 @Override() 423 public void toString(final StringBuilder buffer) 424 { 425 if (stringRepresentation != null) 426 { 427 buffer.append(stringRepresentation); 428 return; 429 } 430 431 buffer.append("[ "); 432 433 final Iterator<JSONValue> iterator = values.iterator(); 434 while (iterator.hasNext()) 435 { 436 iterator.next().toString(buffer); 437 if (iterator.hasNext()) 438 { 439 buffer.append(','); 440 } 441 buffer.append(' '); 442 } 443 444 buffer.append(']'); 445 } 446 447 448 449 /** 450 * Retrieves a single-line string representation of this array as it should 451 * appear in a JSON object, including the surrounding square brackets. 452 * Appropriate encoding will also be used for all elements in the array. 453 * 454 * @return A string representation of this array as it should appear in a 455 * JSON object, including the surrounding square brackets. 456 */ 457 @Override() 458 public String toSingleLineString() 459 { 460 final StringBuilder buffer = new StringBuilder(); 461 toSingleLineString(buffer); 462 return buffer.toString(); 463 } 464 465 466 467 /** 468 * Appends a single-line string representation of this array as it should 469 * appear in a JSON object, including the surrounding square brackets, to the 470 * provided buffer. Appropriate encoding will also be used for all elements 471 * in the array. 472 * 473 * @param buffer The buffer to which the information should be appended. 474 */ 475 @Override() 476 public void toSingleLineString(final StringBuilder buffer) 477 { 478 buffer.append("[ "); 479 480 final Iterator<JSONValue> iterator = values.iterator(); 481 while (iterator.hasNext()) 482 { 483 iterator.next().toSingleLineString(buffer); 484 if (iterator.hasNext()) 485 { 486 buffer.append(','); 487 } 488 buffer.append(' '); 489 } 490 491 buffer.append(']'); 492 } 493 494 495 496 /** 497 * Retrieves a normalized string representation of this array. The normalized 498 * representation will not contain any line breaks, will not include any 499 * spaces around the enclosing brackets or around commas used to separate the 500 * elements, and it will use the normalized representations of those elements. 501 * The order of elements in an array is considered significant, and will not 502 * be affected by the normalization process. 503 * 504 * @return A normalized string representation of this array. 505 */ 506 @Override() 507 public String toNormalizedString() 508 { 509 final StringBuilder buffer = new StringBuilder(); 510 toNormalizedString(buffer); 511 return buffer.toString(); 512 } 513 514 515 516 /** 517 * Appends a normalized string representation of this array to the provided 518 * buffer. The normalized representation will not contain any line breaks, 519 * will not include any spaces around the enclosing brackets or around commas 520 * used to separate the elements, and it will use the normalized 521 * representations of those elements. The order of elements in an array is 522 * considered significant, and will not be affected by the normalization 523 * process. 524 * 525 * @param buffer The buffer to which the information should be appended. 526 */ 527 @Override() 528 public void toNormalizedString(final StringBuilder buffer) 529 { 530 toNormalizedString(buffer, false, true, false); 531 } 532 533 534 535 /** 536 * Retrieves a normalized string representation of this array. The normalized 537 * representation will not contain any line breaks, will not include any 538 * spaces around the enclosing brackets or around commas used to separate the 539 * elements, and it will use the normalized representations of those elements. 540 * The order of elements in an array is considered significant, and will not 541 * be affected by the normalization process. 542 * 543 * @param ignoreFieldNameCase Indicates whether field names should be 544 * treated in a case-sensitive (if {@code false}) 545 * or case-insensitive (if {@code true}) manner. 546 * @param ignoreValueCase Indicates whether string field values should 547 * be treated in a case-sensitive (if 548 * {@code false}) or case-insensitive (if 549 * {@code true}) manner. 550 * @param ignoreArrayOrder Indicates whether the order of elements in an 551 * array should be considered significant (if 552 * {@code false}) or insignificant (if 553 * {@code true}). 554 * 555 * @return A normalized string representation of this array. 556 */ 557 @Override() 558 public String toNormalizedString(final boolean ignoreFieldNameCase, 559 final boolean ignoreValueCase, 560 final boolean ignoreArrayOrder) 561 { 562 final StringBuilder buffer = new StringBuilder(); 563 toNormalizedString(buffer, ignoreFieldNameCase, ignoreValueCase, 564 ignoreArrayOrder); 565 return buffer.toString(); 566 } 567 568 569 570 /** 571 * Appends a normalized string representation of this array to the provided 572 * buffer. The normalized representation will not contain any line breaks, 573 * will not include any spaces around the enclosing brackets or around commas 574 * used to separate the elements, and it will use the normalized 575 * representations of those elements. The order of elements in an array is 576 * considered significant, and will not be affected by the normalization 577 * process. 578 * 579 * @param buffer The buffer to which the information should be 580 * appended. 581 * @param ignoreFieldNameCase Indicates whether field names should be 582 * treated in a case-sensitive (if {@code false}) 583 * or case-insensitive (if {@code true}) manner. 584 * @param ignoreValueCase Indicates whether string field values should 585 * be treated in a case-sensitive (if 586 * {@code false}) or case-insensitive (if 587 * {@code true}) manner. 588 * @param ignoreArrayOrder Indicates whether the order of elements in an 589 * array should be considered significant (if 590 * {@code false}) or insignificant (if 591 * {@code true}). 592 */ 593 @Override() 594 public void toNormalizedString(final StringBuilder buffer, 595 final boolean ignoreFieldNameCase, 596 final boolean ignoreValueCase, 597 final boolean ignoreArrayOrder) 598 { 599 final List<String> normalizedValues = new ArrayList<>(values.size()); 600 for (final JSONValue v : values) 601 { 602 normalizedValues.add(v.toNormalizedString(ignoreFieldNameCase, 603 ignoreValueCase, ignoreArrayOrder)); 604 } 605 606 if (ignoreArrayOrder) 607 { 608 Collections.sort(normalizedValues); 609 } 610 611 buffer.append('['); 612 613 final Iterator<String> iterator = normalizedValues.iterator(); 614 while (iterator.hasNext()) 615 { 616 buffer.append(iterator.next()); 617 if (iterator.hasNext()) 618 { 619 buffer.append(','); 620 } 621 } 622 623 buffer.append(']'); 624 } 625 626 627 628 /** 629 * {@inheritDoc} 630 */ 631 @Override() 632 public void appendToJSONBuffer(final JSONBuffer buffer) 633 { 634 buffer.beginArray(); 635 636 for (final JSONValue value : values) 637 { 638 value.appendToJSONBuffer(buffer); 639 } 640 641 buffer.endArray(); 642 } 643 644 645 646 /** 647 * {@inheritDoc} 648 */ 649 @Override() 650 public void appendToJSONBuffer(final String fieldName, 651 final JSONBuffer buffer) 652 { 653 buffer.beginArray(fieldName); 654 655 for (final JSONValue value : values) 656 { 657 value.appendToJSONBuffer(buffer); 658 } 659 660 buffer.endArray(); 661 } 662}