1 /* 2 * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 27 package javax.management.openmbean; 28 29 30 // java import 31 // 32 import com.sun.jmx.mbeanserver.GetPropertyAction; 33 import com.sun.jmx.mbeanserver.Util; 34 import java.io.IOException; 35 import java.io.ObjectInputStream; 36 import java.io.Serializable; 37 import java.security.AccessController; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Collection; 41 import java.util.Collections; 42 import java.util.HashMap; 43 import java.util.Iterator; 44 import java.util.LinkedHashMap; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Set; 48 import jdk.internal.misc.SharedSecrets; 49 50 // jmx import 51 // 52 53 54 /** 55 * The {@code TabularDataSupport} class is the <i>open data</i> 56 * class which implements the {@code TabularData} 57 * and the {@code Map} interfaces, and which is internally based on a hash map data structure. 58 * 59 * @since 1.5 60 */ 61 /* It would make much more sense to implement 62 Map<List<?>,CompositeData> here, but unfortunately we cannot for 63 compatibility reasons. If we did that, then we would have to 64 define e.g. 65 CompositeData remove(Object) 66 instead of 67 Object remove(Object). 68 69 That would mean that if any existing code subclassed 70 TabularDataSupport and overrode 71 Object remove(Object), 72 it would (a) no longer compile and (b) not actually override 73 CompositeData remove(Object) 74 in binaries compiled before the change. 75 */ 76 public class TabularDataSupport 77 implements TabularData, Map<Object,Object>, 78 Cloneable, Serializable { 79 80 81 /* Serial version */ 82 static final long serialVersionUID = 5720150593236309827L; 83 84 85 /** 86 * @serial This tabular data instance's contents: a {@link HashMap} 87 */ 88 // field cannot be final because of clone method 89 private Map<Object,CompositeData> dataMap; 90 91 /** 92 * @serial This tabular data instance's tabular type 93 */ 94 private final TabularType tabularType; 95 96 /** 97 * The array of item names that define the index used for rows (convenience field) 98 */ 99 private transient String[] indexNamesArray; 100 101 102 103 /* *** Constructors *** */ 104 105 106 /** 107 * Creates an empty {@code TabularDataSupport} instance 108 * whose open-type is <var>tabularType</var>, 109 * and whose underlying {@code HashMap} has a default 110 * initial capacity (101) and default load factor (0.75). 111 * <p> 112 * This constructor simply calls {@code this(tabularType, 101, 0.75f);} 113 * 114 * @param tabularType the <i>tabular type</i> describing this 115 * {@code TabularData} instance; cannot be null. 116 * 117 * @throws IllegalArgumentException if the tabular type is null. 118 */ 119 public TabularDataSupport(TabularType tabularType) { 120 121 this(tabularType, 16, 0.75f); 122 } 123 124 /** 125 * Creates an empty {@code TabularDataSupport} instance whose open-type is <var>tabularType</var>, 126 * and whose underlying {@code HashMap} has the specified initial capacity and load factor. 127 * 128 * @param tabularType the <i>tabular type</i> describing this {@code TabularData} instance; 129 * cannot be null. 130 * 131 * @param initialCapacity the initial capacity of the HashMap. 132 * 133 * @param loadFactor the load factor of the HashMap 134 * 135 * @throws IllegalArgumentException if the initial capacity is less than zero, 136 * or the load factor is nonpositive, 137 * or the tabular type is null. 138 */ 139 public TabularDataSupport(TabularType tabularType, int initialCapacity, float loadFactor) { 140 141 // Check tabularType is not null 142 // 143 if (tabularType == null) { 144 throw new IllegalArgumentException("Argument tabularType cannot be null."); 145 } 146 147 // Initialize this.tabularType (and indexNamesArray for convenience) 148 // 149 this.tabularType = tabularType; 150 List<String> tmpNames = tabularType.getIndexNames(); 151 this.indexNamesArray = tmpNames.toArray(new String[tmpNames.size()]); 152 153 // Since LinkedHashMap was introduced in SE 1.4, it's conceivable even 154 // if very unlikely that we might be the server of a 1.3 client. In 155 // that case you'll need to set this property. See CR 6334663. 156 String useHashMapProp = AccessController.doPrivileged( 157 new GetPropertyAction("jmx.tabular.data.hash.map")); 158 boolean useHashMap = "true".equalsIgnoreCase(useHashMapProp); 159 160 // Construct the empty contents HashMap 161 // 162 this.dataMap = useHashMap ? 163 new HashMap<Object,CompositeData>(initialCapacity, loadFactor) : 164 new LinkedHashMap<Object, CompositeData>(initialCapacity, loadFactor); 165 } 166 167 168 169 170 /* *** TabularData specific information methods *** */ 171 172 173 /** 174 * Returns the <i>tabular type</i> describing this {@code TabularData} instance. 175 */ 176 public TabularType getTabularType() { 177 178 return tabularType; 179 } 180 181 /** 182 * Calculates the index that would be used in this {@code TabularData} instance to refer 183 * to the specified composite data <var>value</var> parameter if it were added to this instance. 184 * This method checks for the type validity of the specified <var>value</var>, 185 * but does not check if the calculated index is already used 186 * to refer to a value in this {@code TabularData} instance. 187 * 188 * @param value the composite data value whose index in this 189 * {@code TabularData} instance is to be calculated; 190 * must be of the same composite type as this instance's row type; 191 * must not be null. 192 * 193 * @return the index that the specified <var>value</var> would have in this {@code TabularData} instance. 194 * 195 * @throws NullPointerException if <var>value</var> is {@code null}. 196 * 197 * @throws InvalidOpenTypeException if <var>value</var> does not conform to this {@code TabularData} instance's 198 * row type definition. 199 */ 200 public Object[] calculateIndex(CompositeData value) { 201 202 // Check value is valid 203 // 204 checkValueType(value); 205 206 // Return its calculated index 207 // 208 return internalCalculateIndex(value).toArray(); 209 } 210 211 212 213 214 /* *** Content information query methods *** */ 215 216 217 /** 218 * Returns {@code true} if and only if this {@code TabularData} instance contains a {@code CompositeData} value 219 * (ie a row) whose index is the specified <var>key</var>. If <var>key</var> cannot be cast to a one dimension array 220 * of Object instances, this method simply returns {@code false}; otherwise it returns the result of the call to 221 * {@code this.containsKey((Object[]) key)}. 222 * 223 * @param key the index value whose presence in this {@code TabularData} instance is to be tested. 224 * 225 * @return {@code true} if this {@code TabularData} indexes a row value with the specified key. 226 */ 227 public boolean containsKey(Object key) { 228 229 // if key is not an array of Object instances, return false 230 // 231 Object[] k; 232 try { 233 k = (Object[]) key; 234 } catch (ClassCastException e) { 235 return false; 236 } 237 238 return this.containsKey(k); 239 } 240 241 /** 242 * Returns {@code true} if and only if this {@code TabularData} instance contains a {@code CompositeData} value 243 * (ie a row) whose index is the specified <var>key</var>. If <var>key</var> is {@code null} or does not conform to 244 * this {@code TabularData} instance's {@code TabularType} definition, this method simply returns {@code false}. 245 * 246 * @param key the index value whose presence in this {@code TabularData} instance is to be tested. 247 * 248 * @return {@code true} if this {@code TabularData} indexes a row value with the specified key. 249 */ 250 public boolean containsKey(Object[] key) { 251 252 return ( key == null ? false : dataMap.containsKey(Arrays.asList(key))); 253 } 254 255 /** 256 * Returns {@code true} if and only if this {@code TabularData} instance contains the specified 257 * {@code CompositeData} value. If <var>value</var> is {@code null} or does not conform to 258 * this {@code TabularData} instance's row type definition, this method simply returns {@code false}. 259 * 260 * @param value the row value whose presence in this {@code TabularData} instance is to be tested. 261 * 262 * @return {@code true} if this {@code TabularData} instance contains the specified row value. 263 */ 264 public boolean containsValue(CompositeData value) { 265 266 return dataMap.containsValue(value); 267 } 268 269 /** 270 * Returns {@code true} if and only if this {@code TabularData} instance contains the specified 271 * value. 272 * 273 * @param value the row value whose presence in this {@code TabularData} instance is to be tested. 274 * 275 * @return {@code true} if this {@code TabularData} instance contains the specified row value. 276 */ 277 public boolean containsValue(Object value) { 278 279 return dataMap.containsValue(value); 280 } 281 282 /** 283 * This method simply calls {@code get((Object[]) key)}. 284 * 285 * @throws NullPointerException if the <var>key</var> is {@code null} 286 * @throws ClassCastException if the <var>key</var> is not of the type {@code Object[]} 287 * @throws InvalidKeyException if the <var>key</var> does not conform 288 * to this {@code TabularData} instance's 289 * {@code TabularType} definition 290 */ 291 public Object get(Object key) { 292 293 return get((Object[]) key); 294 } 295 296 /** 297 * Returns the {@code CompositeData} value whose index is 298 * <var>key</var>, or {@code null} if there is no value mapping 299 * to <var>key</var>, in this {@code TabularData} instance. 300 * 301 * @param key the index of the value to get in this 302 * {@code TabularData} instance; must be valid with this 303 * {@code TabularData} instance's row type definition; must not 304 * be null. 305 * 306 * @return the value corresponding to <var>key</var>. 307 * 308 * @throws NullPointerException if the <var>key</var> is {@code null} 309 * @throws InvalidKeyException if the <var>key</var> does not conform 310 * to this {@code TabularData} instance's 311 * {@code TabularType} type definition. 312 */ 313 public CompositeData get(Object[] key) { 314 315 // Check key is not null and valid with tabularType 316 // (throws NullPointerException, InvalidKeyException) 317 // 318 checkKeyType(key); 319 320 // Return the mapping stored in the parent HashMap 321 // 322 return dataMap.get(Arrays.asList(key)); 323 } 324 325 326 327 328 /* *** Content modification operations (one element at a time) *** */ 329 330 331 /** 332 * This method simply calls {@code put((CompositeData) value)} and 333 * therefore ignores its <var>key</var> parameter which can be {@code null}. 334 * 335 * @param key an ignored parameter. 336 * @param value the {@link CompositeData} to put. 337 * 338 * @return the value which is put 339 * 340 * @throws NullPointerException if the <var>value</var> is {@code null} 341 * @throws ClassCastException if the <var>value</var> is not of 342 * the type {@code CompositeData} 343 * @throws InvalidOpenTypeException if the <var>value</var> does 344 * not conform to this {@code TabularData} instance's 345 * {@code TabularType} definition 346 * @throws KeyAlreadyExistsException if the key for the 347 * <var>value</var> parameter, calculated according to this 348 * {@code TabularData} instance's {@code TabularType} definition 349 * already maps to an existing value 350 */ 351 public Object put(Object key, Object value) { 352 internalPut((CompositeData) value); 353 return value; // should be return internalPut(...); (5090566) 354 } 355 356 public void put(CompositeData value) { 357 internalPut(value); 358 } 359 360 private CompositeData internalPut(CompositeData value) { 361 // Check value is not null, value's type is the same as this instance's row type, 362 // and calculate the value's index according to this instance's tabularType and 363 // check it is not already used for a mapping in the parent HashMap 364 // 365 List<?> index = checkValueAndIndex(value); 366 367 // store the (key, value) mapping in the dataMap HashMap 368 // 369 return dataMap.put(index, value); 370 } 371 372 /** 373 * This method simply calls {@code remove((Object[]) key)}. 374 * 375 * @param key an {@code Object[]} representing the key to remove. 376 * 377 * @return previous value associated with specified key, or {@code null} 378 * if there was no mapping for key. 379 * 380 * @throws NullPointerException if the <var>key</var> is {@code null} 381 * @throws ClassCastException if the <var>key</var> is not of the type {@code Object[]} 382 * @throws InvalidKeyException if the <var>key</var> does not conform to this {@code TabularData} instance's 383 * {@code TabularType} definition 384 */ 385 public Object remove(Object key) { 386 387 return remove((Object[]) key); 388 } 389 390 /** 391 * Removes the {@code CompositeData} value whose index is <var>key</var> from this {@code TabularData} instance, 392 * and returns the removed value, or returns {@code null} if there is no value whose index is <var>key</var>. 393 * 394 * @param key the index of the value to get in this {@code TabularData} instance; 395 * must be valid with this {@code TabularData} instance's row type definition; 396 * must not be null. 397 * 398 * @return previous value associated with specified key, or {@code null} 399 * if there was no mapping for key. 400 * 401 * @throws NullPointerException if the <var>key</var> is {@code null} 402 * @throws InvalidKeyException if the <var>key</var> does not conform to this {@code TabularData} instance's 403 * {@code TabularType} definition 404 */ 405 public CompositeData remove(Object[] key) { 406 407 // Check key is not null and valid with tabularType 408 // (throws NullPointerException, InvalidKeyException) 409 // 410 checkKeyType(key); 411 412 // Removes the (key, value) mapping in the parent HashMap 413 // 414 return dataMap.remove(Arrays.asList(key)); 415 } 416 417 418 419 /* *** Content modification bulk operations *** */ 420 421 422 /** 423 * Add all the values contained in the specified map <var>t</var> 424 * to this {@code TabularData} instance. This method converts 425 * the collection of values contained in this map into an array of 426 * {@code CompositeData} values, if possible, and then call the 427 * method {@code putAll(CompositeData[])}. Note that the keys 428 * used in the specified map <var>t</var> are ignored. This method 429 * allows, for example to add the content of another 430 * {@code TabularData} instance with the same row type (but 431 * possibly different index names) into this instance. 432 * 433 * @param t the map whose values are to be added as new rows to 434 * this {@code TabularData} instance; if <var>t</var> is 435 * {@code null} or empty, this method returns without doing 436 * anything. 437 * 438 * @throws NullPointerException if a value in <var>t</var> is 439 * {@code null}. 440 * @throws ClassCastException if a value in <var>t</var> is not an 441 * instance of {@code CompositeData}. 442 * @throws InvalidOpenTypeException if a value in <var>t</var> 443 * does not conform to this {@code TabularData} instance's row 444 * type definition. 445 * @throws KeyAlreadyExistsException if the index for a value in 446 * <var>t</var>, calculated according to this 447 * {@code TabularData} instance's {@code TabularType} definition 448 * already maps to an existing value in this instance, or two 449 * values in <var>t</var> have the same index. 450 */ 451 public void putAll(Map<?,?> t) { 452 453 // if t is null or empty, just return 454 // 455 if ( (t == null) || (t.size() == 0) ) { 456 return; 457 } 458 459 // Convert the values in t into an array of {@code CompositeData} 460 // 461 CompositeData[] values; 462 try { 463 values = 464 t.values().toArray(new CompositeData[t.size()]); 465 } catch (java.lang.ArrayStoreException e) { 466 throw new ClassCastException("Map argument t contains values which are not instances of {@code CompositeData}"); 467 } 468 469 // Add the array of values 470 // 471 putAll(values); 472 } 473 474 /** 475 * Add all the elements in <var>values</var> to this 476 * {@code TabularData} instance. If any element in 477 * <var>values</var> does not satisfy the constraints defined in 478 * {@link #put(CompositeData) put}, or if any two 479 * elements in <var>values</var> have the same index calculated 480 * according to this {@code TabularData} instance's 481 * {@code TabularType} definition, then an exception describing 482 * the failure is thrown and no element of <var>values</var> is 483 * added, thus leaving this {@code TabularData} instance 484 * unchanged. 485 * 486 * @param values the array of composite data values to be added as 487 * new rows to this {@code TabularData} instance; if 488 * <var>values</var> is {@code null} or empty, this method 489 * returns without doing anything. 490 * 491 * @throws NullPointerException if an element of <var>values</var> 492 * is {@code null} 493 * @throws InvalidOpenTypeException if an element of 494 * <var>values</var> does not conform to this 495 * {@code TabularData} instance's row type definition (ie its 496 * {@code TabularType} definition) 497 * @throws KeyAlreadyExistsException if the index for an element 498 * of <var>values</var>, calculated according to this 499 * {@code TabularData} instance's {@code TabularType} definition 500 * already maps to an existing value in this instance, or two 501 * elements of <var>values</var> have the same index 502 */ 503 public void putAll(CompositeData[] values) { 504 505 // if values is null or empty, just return 506 // 507 if ( (values == null) || (values.length == 0) ) { 508 return; 509 } 510 511 // create the list of indexes corresponding to each value 512 List<List<?>> indexes = 513 new ArrayList<List<?>>(values.length + 1); 514 515 // Check all elements in values and build index list 516 // 517 List<?> index; 518 for (int i=0; i<values.length; i++) { 519 // check value and calculate index 520 index = checkValueAndIndex(values[i]); 521 // check index is different of those previously calculated 522 if (indexes.contains(index)) { 523 throw new KeyAlreadyExistsException("Argument elements values["+ i +"] and values["+ indexes.indexOf(index) + 524 "] have the same indexes, "+ 525 "calculated according to this TabularData instance's tabularType."); 526 } 527 // add to index list 528 indexes.add(index); 529 } 530 531 // store all (index, value) mappings in the dataMap HashMap 532 // 533 for (int i=0; i<values.length; i++) { 534 dataMap.put(indexes.get(i), values[i]); 535 } 536 } 537 538 /** 539 * Removes all rows from this {@code TabularDataSupport} instance. 540 */ 541 public void clear() { 542 543 dataMap.clear(); 544 } 545 546 547 548 /* *** Informational methods from java.util.Map *** */ 549 550 /** 551 * Returns the number of rows in this {@code TabularDataSupport} instance. 552 * 553 * @return the number of rows in this {@code TabularDataSupport} instance. 554 */ 555 public int size() { 556 557 return dataMap.size(); 558 } 559 560 /** 561 * Returns {@code true} if this {@code TabularDataSupport} instance contains no rows. 562 * 563 * @return {@code true} if this {@code TabularDataSupport} instance contains no rows. 564 */ 565 public boolean isEmpty() { 566 567 return (this.size() == 0); 568 } 569 570 571 572 /* *** Collection views from java.util.Map *** */ 573 574 /** 575 * Returns a set view of the keys contained in the underlying map of this 576 * {@code TabularDataSupport} instance used to index the rows. 577 * Each key contained in this {@code Set} is an unmodifiable {@code List<?>} 578 * so the returned set view is a {@code Set<List<?>>} but is declared as a 579 * {@code Set<Object>} for compatibility reasons. 580 * The set is backed by the underlying map of this 581 * {@code TabularDataSupport} instance, so changes to the 582 * {@code TabularDataSupport} instance are reflected in the 583 * set, and vice-versa. 584 * 585 * The set supports element removal, which removes the corresponding 586 * row from this {@code TabularDataSupport} instance, via the 587 * {@link Iterator#remove}, {@link Set#remove}, {@link Set#removeAll}, 588 * {@link Set#retainAll}, and {@link Set#clear} operations. It does 589 * not support the {@link Set#add} or {@link Set#addAll} operations. 590 * 591 * @return a set view ({@code Set<List<?>>}) of the keys used to index 592 * the rows of this {@code TabularDataSupport} instance. 593 */ 594 public Set<Object> keySet() { 595 596 return dataMap.keySet() ; 597 } 598 599 /** 600 * Returns a collection view of the rows contained in this 601 * {@code TabularDataSupport} instance. The returned {@code Collection} 602 * is a {@code Collection<CompositeData>} but is declared as a 603 * {@code Collection<Object>} for compatibility reasons. 604 * The returned collection can be used to iterate over the values. 605 * The collection is backed by the underlying map, so changes to the 606 * {@code TabularDataSupport} instance are reflected in the collection, 607 * and vice-versa. 608 * 609 * The collection supports element removal, which removes the corresponding 610 * index to row mapping from this {@code TabularDataSupport} instance, via 611 * the {@link Iterator#remove}, {@link Collection#remove}, 612 * {@link Collection#removeAll}, {@link Collection#retainAll}, 613 * and {@link Collection#clear} operations. It does not support 614 * the {@link Collection#add} or {@link Collection#addAll} operations. 615 * 616 * @return a collection view ({@code Collection<CompositeData>}) of 617 * the values contained in this {@code TabularDataSupport} instance. 618 */ 619 @SuppressWarnings("unchecked") // historical confusion about the return type 620 public Collection<Object> values() { 621 622 return Util.cast(dataMap.values()); 623 } 624 625 626 /** 627 * Returns a collection view of the index to row mappings 628 * contained in this {@code TabularDataSupport} instance. 629 * Each element in the returned collection is 630 * a {@code Map.Entry<List<?>,CompositeData>} but 631 * is declared as a {@code Map.Entry<Object,Object>} 632 * for compatibility reasons. Each of the map entry 633 * keys is an unmodifiable {@code List<?>}. 634 * The collection is backed by the underlying map of this 635 * {@code TabularDataSupport} instance, so changes to the 636 * {@code TabularDataSupport} instance are reflected in 637 * the collection, and vice-versa. 638 * The collection supports element removal, which removes 639 * the corresponding mapping from the map, via the 640 * {@link Iterator#remove}, {@link Collection#remove}, 641 * {@link Collection#removeAll}, {@link Collection#retainAll}, 642 * and {@link Collection#clear} operations. It does not support 643 * the {@link Collection#add} or {@link Collection#addAll} 644 * operations. 645 * <p> 646 * <b>IMPORTANT NOTICE</b>: Do not use the {@code setValue} method of the 647 * {@code Map.Entry} elements contained in the returned collection view. 648 * Doing so would corrupt the index to row mappings contained in this 649 * {@code TabularDataSupport} instance. 650 * 651 * @return a collection view ({@code Set<Map.Entry<List<?>,CompositeData>>}) 652 * of the mappings contained in this map. 653 * @see java.util.Map.Entry 654 */ 655 @SuppressWarnings("unchecked") // historical confusion about the return type 656 public Set<Map.Entry<Object,Object>> entrySet() { 657 658 return Util.cast(dataMap.entrySet()); 659 } 660 661 662 /* *** Commodity methods from java.lang.Object *** */ 663 664 665 /** 666 * Returns a clone of this {@code TabularDataSupport} instance: 667 * the clone is obtained by calling {@code super.clone()}, and then cloning the underlying map. 668 * Only a shallow clone of the underlying map is made, i.e. 669 * no cloning of the indexes and row values is made as they are immutable. 670 */ 671 /* We cannot use covariance here and return TabularDataSupport 672 because this would fail with existing code that subclassed 673 TabularDataSupport and overrode Object clone(). It would not 674 override the new clone(). */ 675 public Object clone() { 676 try { 677 TabularDataSupport c = (TabularDataSupport) super.clone(); 678 c.dataMap = new HashMap<Object,CompositeData>(c.dataMap); 679 return c; 680 } 681 catch (CloneNotSupportedException e) { 682 throw new InternalError(e.toString(), e); 683 } 684 } 685 686 687 /** 688 * Compares the specified <var>obj</var> parameter with this {@code TabularDataSupport} instance for equality. 689 * <p> 690 * Returns {@code true} if and only if all of the following statements are true: 691 * <ul> 692 * <li><var>obj</var> is non null,</li> 693 * <li><var>obj</var> also implements the {@code TabularData} interface,</li> 694 * <li>their tabular types are equal</li> 695 * <li>their contents (ie all CompositeData values) are equal.</li> 696 * </ul> 697 * This ensures that this {@code equals} method works properly for <var>obj</var> parameters which are 698 * different implementations of the {@code TabularData} interface. 699 * <br> 700 * @param obj the object to be compared for equality with this {@code TabularDataSupport} instance; 701 * 702 * @return {@code true} if the specified object is equal to this {@code TabularDataSupport} instance. 703 */ 704 public boolean equals(Object obj) { 705 706 // if obj is null, return false 707 // 708 if (obj == null) { 709 return false; 710 } 711 712 // if obj is not a TabularData, return false 713 // 714 TabularData other; 715 try { 716 other = (TabularData) obj; 717 } catch (ClassCastException e) { 718 return false; 719 } 720 721 // Now, really test for equality between this TabularData implementation and the other: 722 // 723 724 // their tabularType should be equal 725 if ( ! this.getTabularType().equals(other.getTabularType()) ) { 726 return false; 727 } 728 729 // their contents should be equal: 730 // . same size 731 // . values in this instance are in the other (we know there are no duplicate elements possible) 732 // (row values comparison is enough, because keys are calculated according to tabularType) 733 734 if (this.size() != other.size()) { 735 return false; 736 } 737 for (CompositeData value : dataMap.values()) { 738 if ( ! other.containsValue(value) ) { 739 return false; 740 } 741 } 742 743 // All tests for equality were successfull 744 // 745 return true; 746 } 747 748 /** 749 * Returns the hash code value for this {@code TabularDataSupport} instance. 750 * <p> 751 * The hash code of a {@code TabularDataSupport} instance is the sum of the hash codes 752 * of all elements of information used in {@code equals} comparisons 753 * (ie: its <i>tabular type</i> and its content, where the content is defined as all the CompositeData values). 754 * <p> 755 * This ensures that {@code t1.equals(t2)} implies that {@code t1.hashCode()==t2.hashCode()} 756 * for any two {@code TabularDataSupport} instances {@code t1} and {@code t2}, 757 * as required by the general contract of the method 758 * {@link Object#hashCode() Object.hashCode()}. 759 * <p> 760 * However, note that another instance of a class implementing the {@code TabularData} interface 761 * may be equal to this {@code TabularDataSupport} instance as defined by {@link #equals}, 762 * but may have a different hash code if it is calculated differently. 763 * 764 * @return the hash code value for this {@code TabularDataSupport} instance 765 */ 766 public int hashCode() { 767 768 int result = 0; 769 770 result += this.tabularType.hashCode(); 771 for (Object value : values()) 772 result += value.hashCode(); 773 774 return result; 775 776 } 777 778 /** 779 * Returns a string representation of this {@code TabularDataSupport} instance. 780 * <p> 781 * The string representation consists of the name of this class 782 * (ie {@code javax.management.openmbean.TabularDataSupport}), 783 * the string representation of the tabular type of this instance, and the string representation of the contents 784 * (ie list the key=value mappings as returned by a call to 785 * {@code dataMap.}{@link java.util.HashMap#toString() toString()}). 786 * 787 * @return a string representation of this {@code TabularDataSupport} instance 788 */ 789 public String toString() { 790 791 return new StringBuilder() 792 .append(this.getClass().getName()) 793 .append("(tabularType=") 794 .append(tabularType.toString()) 795 .append(",contents=") 796 .append(dataMap.toString()) 797 .append(")") 798 .toString(); 799 } 800 801 802 803 804 /* *** TabularDataSupport internal utility methods *** */ 805 806 807 /** 808 * Returns the index for value, assuming value is valid for this {@code TabularData} instance 809 * (ie value is not null, and its composite type is equal to row type). 810 * 811 * The index is a List, and not an array, so that an 812 * index.equals(otherIndex) call will actually compare contents, 813 * not just the objects references as is done for an array object. 814 * 815 * The returned List is unmodifiable so that once a row has been put 816 * into the dataMap, its index cannot be modified, 817 * for example by a user that would attempt to modify an 818 * index contained in the Set returned by keySet(). 819 */ 820 private List<?> internalCalculateIndex(CompositeData value) { 821 822 return Collections.unmodifiableList(Arrays.asList(value.getAll(this.indexNamesArray))); 823 } 824 825 /** 826 * Checks if the specified key is valid for this {@code TabularData} instance. 827 * 828 * @throws NullPointerException 829 * @throws InvalidOpenTypeException 830 */ 831 private void checkKeyType(Object[] key) { 832 833 // Check key is neither null nor empty 834 // 835 if ( (key == null) || (key.length == 0) ) { 836 throw new NullPointerException("Argument key cannot be null or empty."); 837 } 838 839 /* Now check key is valid with tabularType index and row type definitions: */ 840 841 // key[] should have the size expected for an index 842 // 843 if (key.length != this.indexNamesArray.length) { 844 throw new InvalidKeyException("Argument key's length="+ key.length + 845 " is different from the number of item values, which is "+ indexNamesArray.length + 846 ", specified for the indexing rows in this TabularData instance."); 847 } 848 849 // each element in key[] should be a value for its corresponding open type specified in rowType 850 // 851 OpenType<?> keyElementType; 852 for (int i=0; i<key.length; i++) { 853 keyElementType = tabularType.getRowType().getType(this.indexNamesArray[i]); 854 if ( (key[i] != null) && (! keyElementType.isValue(key[i])) ) { 855 throw new InvalidKeyException("Argument element key["+ i +"] is not a value for the open type expected for "+ 856 "this element of the index, whose name is \""+ indexNamesArray[i] + 857 "\" and whose open type is "+ keyElementType); 858 } 859 } 860 } 861 862 /** 863 * Checks the specified value's type is valid for this {@code TabularData} instance 864 * (ie value is not null, and its composite type is equal to row type). 865 * 866 * @throws NullPointerException 867 * @throws InvalidOpenTypeException 868 */ 869 private void checkValueType(CompositeData value) { 870 871 // Check value is not null 872 // 873 if (value == null) { 874 throw new NullPointerException("Argument value cannot be null."); 875 } 876 877 // if value's type is not the same as this instance's row type, throw InvalidOpenTypeException 878 // 879 if (!tabularType.getRowType().isValue(value)) { 880 throw new InvalidOpenTypeException("Argument value's composite type ["+ value.getCompositeType() + 881 "] is not assignable to "+ 882 "this TabularData instance's row type ["+ tabularType.getRowType() +"]."); 883 } 884 } 885 886 /** 887 * Checks if the specified value can be put (ie added) in this {@code TabularData} instance 888 * (ie value is not null, its composite type is equal to row type, and its index is not already used), 889 * and returns the index calculated for this value. 890 * 891 * The index is a List, and not an array, so that an index.equals(otherIndex) call will actually compare contents, 892 * not just the objects references as is done for an array object. 893 * 894 * @throws NullPointerException 895 * @throws InvalidOpenTypeException 896 * @throws KeyAlreadyExistsException 897 */ 898 private List<?> checkValueAndIndex(CompositeData value) { 899 900 // Check value is valid 901 // 902 checkValueType(value); 903 904 // Calculate value's index according to this instance's tabularType 905 // and check it is not already used for a mapping in the parent HashMap 906 // 907 List<?> index = internalCalculateIndex(value); 908 909 if (dataMap.containsKey(index)) { 910 throw new KeyAlreadyExistsException("Argument value's index, calculated according to this TabularData "+ 911 "instance's tabularType, already refers to a value in this table."); 912 } 913 914 // The check is OK, so return the index 915 // 916 return index; 917 } 918 919 /** 920 * Deserializes a {@link TabularDataSupport} from an {@link ObjectInputStream}. 921 */ 922 private void readObject(ObjectInputStream in) 923 throws IOException, ClassNotFoundException { 924 in.defaultReadObject(); 925 List<String> tmpNames = tabularType.getIndexNames(); 926 int size = tmpNames.size(); 927 SharedSecrets.getJavaObjectInputStreamAccess().checkArray(in, String[].class, size); 928 indexNamesArray = tmpNames.toArray(new String[size]); 929 } 930 }