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