001/******************************************************************************* 002 * Copyright (c) 2013, 2015 EclipseSource. 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining a copy 005 * of this software and associated documentation files (the "Software"), to deal 006 * in the Software without restriction, including without limitation the rights 007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 008 * copies of the Software, and to permit persons to whom the Software is 009 * furnished to do so, subject to the following conditions: 010 * 011 * The above copyright notice and this permission notice shall be included in all 012 * copies or substantial portions of the Software. 013 * 014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 020 * SOFTWARE. 021 ******************************************************************************/ 022package com.restfb.json; 023 024import java.io.IOException; 025import java.io.ObjectInputStream; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.Iterator; 029import java.util.List; 030 031import com.restfb.json.JsonObject.Member; 032 033/** 034 * Represents a JSON object, a set of name/value pairs, where the names are strings and the values are JSON values. 035 * <p> 036 * Members can be added using the <code>add(String, ...)</code> methods which accept instances of {@link JsonValue}, 037 * strings, primitive numbers, and boolean values. To modify certain values of an object, use the 038 * <code>set(String, ...)</code> methods. Please note that the <code>add</code> methods are faster than <code>set</code> 039 * as they do not search for existing members. On the other hand, the <code>add</code> methods do not prevent adding 040 * multiple members with the same name. Duplicate names are discouraged but not prohibited by JSON. 041 * </p> 042 * <p> 043 * Members can be accessed by their name using {@link #get(String)}. A list of all names can be obtained from the method 044 * {@link #names()}. This class also supports iterating over the members in document order using an {@link #iterator()} 045 * or an enhanced for loop: 046 * </p> 047 * 048 * <pre> 049 * for (Member member : jsonObject) { 050 * String name = member.getName(); 051 * JsonValue value = member.getValue(); 052 * ... 053 * } 054 * </pre> 055 * <p> 056 * Even though JSON objects are unordered by definition, instances of this class preserve the order of members to allow 057 * processing in document order and to guarantee a predictable output. 058 * </p> 059 * <p> 060 * Note that this class is <strong>not thread-safe</strong>. If multiple threads access a <code>JsonObject</code> 061 * instance concurrently, while at least one of these threads modifies the contents of this object, access to the 062 * instance must be synchronized externally. Failure to do so may lead to an inconsistent state. 063 * </p> 064 * <p> 065 * This class is <strong>not supposed to be extended</strong> by clients. 066 * </p> 067 */ 068@SuppressWarnings("serial") // use default serial UID 069public class JsonObject extends JsonValue implements Iterable<Member> { 070 071 private final List<String> names; 072 private final List<JsonValue> values; 073 private transient HashIndexTable table; 074 075 // String constants 076 private static final String OBJECT_IS_NULL = "object is null"; 077 private static final String NAME_IS_NULL = "name is null"; 078 private static final String VALUE_IS_NULL = "value is null"; 079 080 /** 081 * Creates a new empty JsonObject. 082 */ 083 public JsonObject() { 084 names = new ArrayList<>(); 085 values = new ArrayList<>(); 086 table = new HashIndexTable(); 087 } 088 089 /** 090 * Creates a new JsonObject, initialized with the contents of the specified JSON object. 091 * 092 * @param object 093 * the JSON object to get the initial contents from, must not be <code>null</code> 094 */ 095 public JsonObject(JsonObject object) { 096 this(object, false); 097 } 098 099 private JsonObject(JsonObject object, boolean unmodifiable) { 100 if (object == null) { 101 throw new NullPointerException(OBJECT_IS_NULL); 102 } 103 if (unmodifiable) { 104 names = Collections.unmodifiableList(object.names); 105 values = Collections.unmodifiableList(object.values); 106 } else { 107 names = new ArrayList<>(object.names); 108 values = new ArrayList<>(object.values); 109 } 110 table = new HashIndexTable(); 111 updateHashIndex(); 112 } 113 114 /** 115 * Returns an unmodifiable JsonObject for the specified one. This method allows to provide read-only access to a 116 * JsonObject. 117 * <p> 118 * The returned JsonObject is backed by the given object and reflect changes that happen to it. Attempts to modify the 119 * returned JsonObject result in an <code>UnsupportedOperationException</code>. 120 * </p> 121 * 122 * @param object 123 * the JsonObject for which an unmodifiable JsonObject is to be returned 124 * @return an unmodifiable view of the specified JsonObject 125 */ 126 public static JsonObject unmodifiableObject(JsonObject object) { 127 return new JsonObject(object, true); 128 } 129 130 /** 131 * Appends a new member to the end of this object, with the specified name and the JSON representation of the 132 * specified <code>int</code> value. 133 * <p> 134 * This method <strong>does not prevent duplicate names</strong>. Calling this method with a name that already exists 135 * in the object will append another member with the same name. In order to replace existing members, use the method 136 * <code>set(name, value)</code> instead. However, <strong> <em>add</em> is much faster than <em>set</em></strong> 137 * (because it does not need to search for existing members). Therefore <em>add</em> should be preferred when 138 * constructing new objects. 139 * </p> 140 * 141 * @param name 142 * the name of the member to add 143 * @param value 144 * the value of the member to add 145 * @return the object itself, to enable method chaining 146 */ 147 public JsonObject add(String name, int value) { 148 add(name, Json.value(value)); 149 return this; 150 } 151 152 /** 153 * Appends a new member to the end of this object, with the specified name and the JSON representation of the 154 * specified <code>long</code> value. 155 * <p> 156 * This method <strong>does not prevent duplicate names</strong>. Calling this method with a name that already exists 157 * in the object will append another member with the same name. In order to replace existing members, use the method 158 * <code>set(name, value)</code> instead. However, <strong> <em>add</em> is much faster than <em>set</em></strong> 159 * (because it does not need to search for existing members). Therefore <em>add</em> should be preferred when 160 * constructing new objects. 161 * </p> 162 * 163 * @param name 164 * the name of the member to add 165 * @param value 166 * the value of the member to add 167 * @return the object itself, to enable method chaining 168 */ 169 public JsonObject add(String name, long value) { 170 add(name, Json.value(value)); 171 return this; 172 } 173 174 /** 175 * Appends a new member to the end of this object, with the specified name and the JSON representation of the 176 * specified <code>float</code> value. 177 * <p> 178 * This method <strong>does not prevent duplicate names</strong>. Calling this method with a name that already exists 179 * in the object will append another member with the same name. In order to replace existing members, use the method 180 * <code>set(name, value)</code> instead. However, <strong> <em>add</em> is much faster than <em>set</em></strong> 181 * (because it does not need to search for existing members). Therefore <em>add</em> should be preferred when 182 * constructing new objects. 183 * </p> 184 * 185 * @param name 186 * the name of the member to add 187 * @param value 188 * the value of the member to add 189 * @return the object itself, to enable method chaining 190 */ 191 public JsonObject add(String name, float value) { 192 add(name, Json.value(value)); 193 return this; 194 } 195 196 /** 197 * Appends a new member to the end of this object, with the specified name and the JSON representation of the 198 * specified <code>double</code> value. 199 * <p> 200 * This method <strong>does not prevent duplicate names</strong>. Calling this method with a name that already exists 201 * in the object will append another member with the same name. In order to replace existing members, use the method 202 * <code>set(name, value)</code> instead. However, <strong> <em>add</em> is much faster than <em>set</em></strong> 203 * (because it does not need to search for existing members). Therefore <em>add</em> should be preferred when 204 * constructing new objects. 205 * </p> 206 * 207 * @param name 208 * the name of the member to add 209 * @param value 210 * the value of the member to add 211 * @return the object itself, to enable method chaining 212 */ 213 public JsonObject add(String name, double value) { 214 add(name, Json.value(value)); 215 return this; 216 } 217 218 /** 219 * Appends a new member to the end of this object, with the specified name and the JSON representation of the 220 * specified <code>boolean</code> value. 221 * <p> 222 * This method <strong>does not prevent duplicate names</strong>. Calling this method with a name that already exists 223 * in the object will append another member with the same name. In order to replace existing members, use the method 224 * <code>set(name, value)</code> instead. However, <strong> <em>add</em> is much faster than <em>set</em></strong> 225 * (because it does not need to search for existing members). Therefore <em>add</em> should be preferred when 226 * constructing new objects. 227 * </p> 228 * 229 * @param name 230 * the name of the member to add 231 * @param value 232 * the value of the member to add 233 * @return the object itself, to enable method chaining 234 */ 235 public JsonObject add(String name, boolean value) { 236 add(name, Json.value(value)); 237 return this; 238 } 239 240 /** 241 * Appends a new member to the end of this object, with the specified name and the JSON representation of the 242 * specified string. 243 * <p> 244 * This method <strong>does not prevent duplicate names</strong>. Calling this method with a name that already exists 245 * in the object will append another member with the same name. In order to replace existing members, use the method 246 * <code>set(name, value)</code> instead. However, <strong> <em>add</em> is much faster than <em>set</em></strong> 247 * (because it does not need to search for existing members). Therefore <em>add</em> should be preferred when 248 * constructing new objects. 249 * </p> 250 * 251 * @param name 252 * the name of the member to add 253 * @param value 254 * the value of the member to add 255 * @return the object itself, to enable method chaining 256 */ 257 public JsonObject add(String name, String value) { 258 add(name, Json.value(value)); 259 return this; 260 } 261 262 /** 263 * Appends a new member to the end of this object, with the specified name and the specified JSON value. 264 * <p> 265 * This method <strong>does not prevent duplicate names</strong>. Calling this method with a name that already exists 266 * in the object will append another member with the same name. In order to replace existing members, use the method 267 * <code>set(name, value)</code> instead. However, <strong> <em>add</em> is much faster than <em>set</em></strong> 268 * (because it does not need to search for existing members). Therefore <em>add</em> should be preferred when 269 * constructing new objects. 270 * </p> 271 * 272 * @param name 273 * the name of the member to add 274 * @param value 275 * the value of the member to add, must not be <code>null</code> 276 * @return the object itself, to enable method chaining 277 */ 278 public JsonObject add(String name, JsonValue value) { 279 if (name == null) { 280 throw new NullPointerException(NAME_IS_NULL); 281 } 282 if (value == null) { 283 throw new NullPointerException(VALUE_IS_NULL); 284 } 285 table.add(name, names.size()); 286 names.add(name); 287 values.add(value); 288 return this; 289 } 290 291 /** 292 * Sets the value of the member with the specified name to the JSON representation of the specified <code>int</code> 293 * value. If this object does not contain a member with this name, a new member is added at the end of the object. If 294 * this object contains multiple members with this name, only the last one is changed. 295 * <p> 296 * This method should <strong>only be used to modify existing objects</strong>. To fill a new object with members, the 297 * method <code>add(name, value)</code> should be preferred which is much faster (as it does not need to search for 298 * existing members). 299 * </p> 300 * 301 * @param name 302 * the name of the member to replace 303 * @param value 304 * the value to set to the member 305 * @return the object itself, to enable method chaining 306 */ 307 public JsonObject set(String name, int value) { 308 set(name, Json.value(value)); 309 return this; 310 } 311 312 /** 313 * Sets the value of the member with the specified name to the JSON representation of the specified <code>long</code> 314 * value. If this object does not contain a member with this name, a new member is added at the end of the object. If 315 * this object contains multiple members with this name, only the last one is changed. 316 * <p> 317 * This method should <strong>only be used to modify existing objects</strong>. To fill a new object with members, the 318 * method <code>add(name, value)</code> should be preferred which is much faster (as it does not need to search for 319 * existing members). 320 * </p> 321 * 322 * @param name 323 * the name of the member to replace 324 * @param value 325 * the value to set to the member 326 * @return the object itself, to enable method chaining 327 */ 328 public JsonObject set(String name, long value) { 329 set(name, Json.value(value)); 330 return this; 331 } 332 333 /** 334 * Sets the value of the member with the specified name to the JSON representation of the specified <code>float</code> 335 * value. If this object does not contain a member with this name, a new member is added at the end of the object. If 336 * this object contains multiple members with this name, only the last one is changed. 337 * <p> 338 * This method should <strong>only be used to modify existing objects</strong>. To fill a new object with members, the 339 * method <code>add(name, value)</code> should be preferred which is much faster (as it does not need to search for 340 * existing members). 341 * </p> 342 * 343 * @param name 344 * the name of the member to add 345 * @param value 346 * the value of the member to add 347 * @return the object itself, to enable method chaining 348 */ 349 public JsonObject set(String name, float value) { 350 set(name, Json.value(value)); 351 return this; 352 } 353 354 /** 355 * Sets the value of the member with the specified name to the JSON representation of the specified 356 * <code>double</code> value. If this object does not contain a member with this name, a new member is added at the 357 * end of the object. If this object contains multiple members with this name, only the last one is changed. 358 * <p> 359 * This method should <strong>only be used to modify existing objects</strong>. To fill a new object with members, the 360 * method <code>add(name, value)</code> should be preferred which is much faster (as it does not need to search for 361 * existing members). 362 * </p> 363 * 364 * @param name 365 * the name of the member to add 366 * @param value 367 * the value of the member to add 368 * @return the object itself, to enable method chaining 369 */ 370 public JsonObject set(String name, double value) { 371 set(name, Json.value(value)); 372 return this; 373 } 374 375 /** 376 * Sets the value of the member with the specified name to the JSON representation of the specified 377 * <code>boolean</code> value. If this object does not contain a member with this name, a new member is added at the 378 * end of the object. If this object contains multiple members with this name, only the last one is changed. 379 * <p> 380 * This method should <strong>only be used to modify existing objects</strong>. To fill a new object with members, the 381 * method <code>add(name, value)</code> should be preferred which is much faster (as it does not need to search for 382 * existing members). 383 * </p> 384 * 385 * @param name 386 * the name of the member to add 387 * @param value 388 * the value of the member to add 389 * @return the object itself, to enable method chaining 390 */ 391 public JsonObject set(String name, boolean value) { 392 set(name, Json.value(value)); 393 return this; 394 } 395 396 /** 397 * Sets the value of the member with the specified name to the JSON representation of the specified string. If this 398 * object does not contain a member with this name, a new member is added at the end of the object. If this object 399 * contains multiple members with this name, only the last one is changed. 400 * <p> 401 * This method should <strong>only be used to modify existing objects</strong>. To fill a new object with members, the 402 * method <code>add(name, value)</code> should be preferred which is much faster (as it does not need to search for 403 * existing members). 404 * </p> 405 * 406 * @param name 407 * the name of the member to add 408 * @param value 409 * the value of the member to add 410 * @return the object itself, to enable method chaining 411 */ 412 public JsonObject set(String name, String value) { 413 set(name, Json.value(value)); 414 return this; 415 } 416 417 /** 418 * Sets the value of the member with the specified name to the specified JSON value. If this object does not contain a 419 * member with this name, a new member is added at the end of the object. If this object contains multiple members 420 * with this name, only the last one is changed. 421 * <p> 422 * This method should <strong>only be used to modify existing objects</strong>. To fill a new object with members, the 423 * method <code>add(name, value)</code> should be preferred which is much faster (as it does not need to search for 424 * existing members). 425 * </p> 426 * 427 * @param name 428 * the name of the member to add 429 * @param value 430 * the value of the member to add, must not be <code>null</code> 431 * @return the object itself, to enable method chaining 432 */ 433 public JsonObject set(String name, JsonValue value) { 434 if (name == null) { 435 throw new NullPointerException(NAME_IS_NULL); 436 } 437 if (value == null) { 438 throw new NullPointerException(VALUE_IS_NULL); 439 } 440 int index = indexOf(name); 441 if (index != -1) { 442 values.set(index, value); 443 } else { 444 table.add(name, names.size()); 445 names.add(name); 446 values.add(value); 447 } 448 return this; 449 } 450 451 /** 452 * Removes a member with the specified name from this object. If this object contains multiple members with the given 453 * name, only the last one is removed. If this object does not contain a member with the specified name, the object is 454 * not modified. 455 * 456 * @param name 457 * the name of the member to remove 458 * @return the object itself, to enable method chaining 459 */ 460 public JsonObject remove(String name) { 461 if (name == null) { 462 throw new NullPointerException(NAME_IS_NULL); 463 } 464 int index = indexOf(name); 465 if (index != -1) { 466 table.remove(index); 467 names.remove(index); 468 values.remove(index); 469 } 470 return this; 471 } 472 473 /** 474 * Checks if a specified member is present as a child of this object. This will not test if 475 * this object contains the literal <code>null</code>, {@link JsonValue#isNull()} should be used 476 * for this purpose. 477 * 478 * @param name 479 * the name of the member to check for 480 * @return whether or not the member is present 481 */ 482 public boolean contains(String name) { 483 return names.contains(name); 484 } 485 486 /** 487 * Copies all members of the specified object into this object. When the specified object contains 488 * members with names that also exist in this object, the existing values in this object will be 489 * replaced by the corresponding values in the specified object. 490 * 491 * @param object 492 * the object to merge 493 * @return the object itself, to enable method chaining 494 */ 495 public JsonObject merge(JsonObject object) { 496 if (object == null) { 497 throw new NullPointerException(OBJECT_IS_NULL); 498 } 499 for (Member member : object) { 500 this.set(member.name, member.value); 501 } 502 return this; 503 } 504 505 /** 506 * Returns the value of the member with the specified name in this object. If this object contains multiple members 507 * with the given name, this method will return the last one. 508 * 509 * @param name 510 * the name of the member whose value is to be returned 511 * @return the value of the last member with the specified name, or <code>null</code> if this object does not contain 512 * a member with that name 513 */ 514 public JsonValue get(String name) { 515 if (name == null) { 516 throw new NullPointerException(NAME_IS_NULL); 517 } 518 int index = indexOf(name); 519 return index != -1 ? values.get(index) : null; 520 } 521 522 /** 523 * Returns the <code>int</code> value of the member with the specified name in this object. If this object does not 524 * contain a member with this name, the given default value is returned. If this object contains multiple members with 525 * the given name, the last one will be picked. If this member's value does not represent a JSON number or if it 526 * cannot be interpreted as Java <code>int</code>, an exception is thrown. 527 * 528 * @param name 529 * the name of the member whose value is to be returned 530 * @param defaultValue 531 * the value to be returned if the requested member is missing 532 * @return the value of the last member with the specified name, or the given default value if this object does not 533 * contain a member with that name 534 */ 535 public int getInt(String name, int defaultValue) { 536 JsonValue value = get(name); 537 return value != null ? value.asInt() : defaultValue; 538 } 539 540 /** 541 * Returns the <code>long</code> value of the member with the specified name in this object. If this object does not 542 * contain a member with this name, the given default value is returned. If this object contains multiple members with 543 * the given name, the last one will be picked. If this member's value does not represent a JSON number or if it 544 * cannot be interpreted as Java <code>long</code>, an exception is thrown. 545 * 546 * @param name 547 * the name of the member whose value is to be returned 548 * @param defaultValue 549 * the value to be returned if the requested member is missing 550 * @return the value of the last member with the specified name, or the given default value if this object does not 551 * contain a member with that name 552 */ 553 public long getLong(String name, long defaultValue) { 554 JsonValue value = get(name); 555 return value != null ? value.asLong() : defaultValue; 556 } 557 558 /** 559 * Returns the <code>float</code> value of the member with the specified name in this object. If this object does not 560 * contain a member with this name, the given default value is returned. If this object contains multiple members with 561 * the given name, the last one will be picked. If this member's value does not represent a JSON number or if it 562 * cannot be interpreted as Java <code>float</code>, an exception is thrown. 563 * 564 * @param name 565 * the name of the member whose value is to be returned 566 * @param defaultValue 567 * the value to be returned if the requested member is missing 568 * @return the value of the last member with the specified name, or the given default value if this object does not 569 * contain a member with that name 570 */ 571 public float getFloat(String name, float defaultValue) { 572 JsonValue value = get(name); 573 return value != null ? value.asFloat() : defaultValue; 574 } 575 576 /** 577 * Returns the <code>double</code> value of the member with the specified name in this object. If this object does not 578 * contain a member with this name, the given default value is returned. If this object contains multiple members with 579 * the given name, the last one will be picked. If this member's value does not represent a JSON number or if it 580 * cannot be interpreted as Java <code>double</code>, an exception is thrown. 581 * 582 * @param name 583 * the name of the member whose value is to be returned 584 * @param defaultValue 585 * the value to be returned if the requested member is missing 586 * @return the value of the last member with the specified name, or the given default value if this object does not 587 * contain a member with that name 588 */ 589 public double getDouble(String name, double defaultValue) { 590 JsonValue value = get(name); 591 return value != null ? value.asDouble() : defaultValue; 592 } 593 594 /** 595 * Returns the <code>boolean</code> value of the member with the specified name in this object. If this object does 596 * not contain a member with this name, the given default value is returned. If this object contains multiple members 597 * with the given name, the last one will be picked. If this member's value does not represent a JSON 598 * <code>true</code> or <code>false</code> value, an exception is thrown. 599 * 600 * @param name 601 * the name of the member whose value is to be returned 602 * @param defaultValue 603 * the value to be returned if the requested member is missing 604 * @return the value of the last member with the specified name, or the given default value if this object does not 605 * contain a member with that name 606 */ 607 public boolean getBoolean(String name, boolean defaultValue) { 608 JsonValue value = get(name); 609 return value != null ? value.asBoolean() : defaultValue; 610 } 611 612 /** 613 * Returns the <code>String</code> value of the member with the specified name in this object. If this object does not 614 * contain a member with this name, the given default value is returned. If this object contains multiple members with 615 * the given name, the last one is picked. If this member's value does not represent a JSON string, an exception is 616 * thrown. 617 * 618 * @param name 619 * the name of the member whose value is to be returned 620 * @param defaultValue 621 * the value to be returned if the requested member is missing 622 * @return the value of the last member with the specified name, or the given default value if this object does not 623 * contain a member with that name 624 */ 625 public String getString(String name, String defaultValue) { 626 JsonValue value = get(name); 627 return value != null ? value.asString() : defaultValue; 628 } 629 630 /** 631 * Returns the number of members (name/value pairs) in this object. 632 * 633 * @return the number of members in this object 634 */ 635 public int size() { 636 return names.size(); 637 } 638 639 /** 640 * Returns <code>true</code> if this object contains no members. 641 * 642 * @return <code>true</code> if this object contains no members 643 */ 644 public boolean isEmpty() { 645 return names.isEmpty(); 646 } 647 648 /** 649 * Returns a list of the names in this object in document order. The returned list is backed by this object and will 650 * reflect subsequent changes. It cannot be used to modify this object. Attempts to modify the returned list will 651 * result in an exception. 652 * 653 * @return a list of the names in this object 654 */ 655 public List<String> names() { 656 return Collections.unmodifiableList(names); 657 } 658 659 /** 660 * Returns an iterator over the members of this object in document order. The returned iterator cannot be used to 661 * modify this object. 662 * 663 * @return an iterator over the members of this object 664 */ 665 public Iterator<Member> iterator() { 666 final Iterator<String> namesIterator = names.iterator(); 667 final Iterator<JsonValue> valuesIterator = values.iterator(); 668 return new Iterator<JsonObject.Member>() { 669 670 @Override 671 public boolean hasNext() { 672 return namesIterator.hasNext(); 673 } 674 675 @Override 676 public Member next() { 677 String name = namesIterator.next(); 678 JsonValue value = valuesIterator.next(); 679 return new Member(name, value); 680 } 681 682 @Override 683 public void remove() { 684 throw new UnsupportedOperationException(); 685 } 686 687 }; 688 } 689 690 @Override 691 void write(JsonWriter writer) throws IOException { 692 writer.writeObjectOpen(); 693 Iterator<String> namesIterator = names.iterator(); 694 Iterator<JsonValue> valuesIterator = values.iterator(); 695 if (namesIterator.hasNext()) { 696 writer.writeMemberName(namesIterator.next()); 697 writer.writeMemberSeparator(); 698 valuesIterator.next().write(writer); 699 while (namesIterator.hasNext()) { 700 writer.writeObjectSeparator(); 701 writer.writeMemberName(namesIterator.next()); 702 writer.writeMemberSeparator(); 703 valuesIterator.next().write(writer); 704 } 705 } 706 writer.writeObjectClose(); 707 } 708 709 @Override 710 public boolean isObject() { 711 return true; 712 } 713 714 @Override 715 public JsonObject asObject() { 716 return this; 717 } 718 719 @Override 720 public int hashCode() { 721 int result = 1; 722 result = 31 * result + names.hashCode(); 723 result = 31 * result + values.hashCode(); 724 return result; 725 } 726 727 @Override 728 public boolean equals(Object obj) { 729 if (this == obj) { 730 return true; 731 } 732 if (obj == null) { 733 return false; 734 } 735 if (getClass() != obj.getClass()) { 736 return false; 737 } 738 JsonObject other = (JsonObject) obj; 739 return names.equals(other.names) && values.equals(other.values); 740 } 741 742 int indexOf(String name) { 743 int index = table.get(name); 744 if (index != -1 && name.equals(names.get(index))) { 745 return index; 746 } 747 return names.lastIndexOf(name); 748 } 749 750 private synchronized void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { 751 inputStream.defaultReadObject(); 752 table = new HashIndexTable(); 753 updateHashIndex(); 754 } 755 756 private void updateHashIndex() { 757 int size = names.size(); 758 for (int i = 0; i < size; i++) { 759 table.add(names.get(i), i); 760 } 761 } 762 763 /** 764 * Represents a member of a JSON object, a pair of a name and a value. 765 */ 766 public static class Member { 767 768 private final String name; 769 private final JsonValue value; 770 771 Member(String name, JsonValue value) { 772 this.name = name; 773 this.value = value; 774 } 775 776 /** 777 * Returns the name of this member. 778 * 779 * @return the name of this member, never <code>null</code> 780 */ 781 public String getName() { 782 return name; 783 } 784 785 /** 786 * Returns the value of this member. 787 * 788 * @return the value of this member, never <code>null</code> 789 */ 790 public JsonValue getValue() { 791 return value; 792 } 793 794 @Override 795 public int hashCode() { 796 int result = 1; 797 result = 31 * result + name.hashCode(); 798 result = 31 * result + value.hashCode(); 799 return result; 800 } 801 802 /** 803 * Indicates whether a given object is "equal to" this JsonObject. An object is considered equal 804 * if it is also a <code>JsonObject</code> and both objects contain the same members <em>in 805 * the same order</em>. 806 * <p> 807 * If two JsonObjects are equal, they will also produce the same JSON output. 808 * </p> 809 * 810 * @param object 811 * the object to be compared with this JsonObject 812 * @return <tt>true</tt> if the specified object is equal to this JsonObject, <code>false</code> 813 * otherwise 814 */ 815 @Override 816 public boolean equals(Object object) { 817 if (this == object) { 818 return true; 819 } 820 if (object == null) { 821 return false; 822 } 823 if (getClass() != object.getClass()) { 824 return false; 825 } 826 Member other = (Member)object; 827 return name.equals(other.name) && value.equals(other.value); 828 } 829 830 } 831 832 static class HashIndexTable { 833 834 private final byte[] hashTable = new byte[32]; // must be a power of two 835 836 public HashIndexTable() { 837 // nothing to do here 838 } 839 840 public HashIndexTable(HashIndexTable original) { 841 System.arraycopy(original.hashTable, 0, hashTable, 0, hashTable.length); 842 } 843 844 void add(String name, int index) { 845 int slot = hashSlotFor(name); 846 if (index < 0xff) { 847 // increment by 1, 0 stands for empty 848 hashTable[slot] = (byte) (index + 1); 849 } else { 850 hashTable[slot] = 0; 851 } 852 } 853 854 void remove(int index) { 855 for (int i = 0; i < hashTable.length; i++) { 856 if (hashTable[i] == index + 1) { 857 hashTable[i] = 0; 858 } else if (hashTable[i] > index + 1) { 859 hashTable[i]--; 860 } 861 } 862 } 863 864 int get(Object name) { 865 int slot = hashSlotFor(name); 866 // subtract 1, 0 stands for empty 867 return (hashTable[slot] & 0xff) - 1; 868 } 869 870 private int hashSlotFor(Object element) { 871 return element.hashCode() & hashTable.length - 1; 872 } 873 874 } 875 876}