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.access.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>&nbsp;
 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 }