TabularDataSupport.java 35.7 KB
Newer Older
D
duke 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
/*
 * Copyright 2000-2007 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */


package javax.management.openmbean;


// java import
//
32
import com.sun.jmx.mbeanserver.GetPropertyAction;
D
duke 已提交
33 34 35
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
36
import java.security.AccessController;
D
duke 已提交
37 38 39 40 41 42
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
43
import java.util.LinkedHashMap;
D
duke 已提交
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
import java.util.List;
import java.util.Map;
import java.util.Set;

// jmx import
//


/**
 * The <tt>TabularDataSupport</tt> class is the <i>open data</i> class which implements the <tt>TabularData</tt>
 * and the <tt>Map</tt> interfaces, and which is internally based on a hash map data structure.
 *
 * @since 1.5
 */
/* It would make much more sense to implement
   Map<List<?>,CompositeData> here, but unfortunately we cannot for
   compatibility reasons.  If we did that, then we would have to
   define e.g.
   CompositeData remove(Object)
   instead of
   Object remove(Object).

   That would mean that if any existing code subclassed
   TabularDataSupport and overrode
   Object remove(Object),
   it would (a) no longer compile and (b) not actually override
   CompositeData remove(Object)
   in binaries compiled before the change.
*/
public class TabularDataSupport
    implements TabularData, Map<Object,Object>,
               Cloneable, Serializable {


    /* Serial version */
    static final long serialVersionUID = 5720150593236309827L;


    /**
     * @serial This tabular data instance's contents: a {@link HashMap}
     */
85
    // field cannot be final because of clone method
D
duke 已提交
86 87 88 89 90
    private Map<Object,CompositeData> dataMap;

    /**
     * @serial This tabular data instance's tabular type
     */
91
    private final TabularType tabularType;
D
duke 已提交
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115

    /**
     * The array of item names that define the index used for rows (convenience field)
     */
    private transient String[] indexNamesArray;



    /* *** Constructors *** */


    /**
     * Creates an empty <tt>TabularDataSupport</tt> instance whose open-type is <var>tabularType</var>,
     * and whose underlying <tt>HashMap</tt> has a default initial capacity (101) and default load factor (0.75).
     * <p>
     * This constructor simply calls <tt>this(tabularType, 101, 0.75f);</tt>
     *
     * @param  tabularType               the <i>tabular type</i> describing this <tt>TabularData</tt> instance;
     *                                   cannot be null.
     *
     * @throws IllegalArgumentException  if the tabular type is null.
     */
    public TabularDataSupport(TabularType tabularType) {

116
        this(tabularType, 16, 0.75f);
D
duke 已提交
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
    }

    /**
     * Creates an empty <tt>TabularDataSupport</tt> instance whose open-type is <var>tabularType</var>,
     * and whose underlying <tt>HashMap</tt> has the specified initial capacity and load factor.
     *
     * @param  tabularType               the <i>tabular type</i> describing this <tt>TabularData</tt> instance;
     *                           cannot be null.
     *
     * @param  initialCapacity   the initial capacity of the HashMap.
     *
     * @param  loadFactor        the load factor of the HashMap
     *
     * @throws IllegalArgumentException  if the initial capacity is less than zero,
     *                                   or the load factor is nonpositive,
     *                                   or the tabular type is null.
     */
    public TabularDataSupport(TabularType tabularType, int initialCapacity, float loadFactor) {

        // Check tabularType is not null
        //
        if (tabularType == null) {
            throw new IllegalArgumentException("Argument tabularType cannot be null.");
        }

        // Initialize this.tabularType (and indexNamesArray for convenience)
        //
        this.tabularType = tabularType;
        List<String> tmpNames = tabularType.getIndexNames();
        this.indexNamesArray = tmpNames.toArray(new String[tmpNames.size()]);

148 149 150 151 152 153 154
        // Since LinkedHashMap was introduced in SE 1.4, it's conceivable even
        // if very unlikely that we might be the server of a 1.3 client.  In
        // that case you'll need to set this property.  See CR 6334663.
        String useHashMapProp = AccessController.doPrivileged(
                new GetPropertyAction("jmx.tabular.data.hash.map"));
        boolean useHashMap = "true".equalsIgnoreCase(useHashMapProp);

D
duke 已提交
155 156
        // Construct the empty contents HashMap
        //
157 158 159
        this.dataMap = useHashMap ?
            new HashMap<Object,CompositeData>(initialCapacity, loadFactor) :
            new LinkedHashMap<Object, CompositeData>(initialCapacity, loadFactor);
D
duke 已提交
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917
    }




    /* *** TabularData specific information methods *** */


    /**
     * Returns the <i>tabular type</i> describing this <tt>TabularData</tt> instance.
     */
    public TabularType getTabularType() {

        return tabularType;
    }

    /**
     * Calculates the index that would be used in this <tt>TabularData</tt> instance to refer to the specified
     * composite data <var>value</var> parameter if it were added to this instance.
     * This method checks for the type validity of the specified <var>value</var>,
     * but does not check if the calculated index is already used to refer to a value in this <tt>TabularData</tt> instance.
     *
     * @param  value                      the composite data value whose index in this
     *                                    <tt>TabularData</tt> instance is to be calculated;
     *                                    must be of the same composite type as this instance's row type;
     *                                    must not be null.
     *
     * @return the index that the specified <var>value</var> would have in this <tt>TabularData</tt> instance.
     *
     * @throws NullPointerException       if <var>value</var> is <tt>null</tt>.
     *
     * @throws InvalidOpenTypeException   if <var>value</var> does not conform to this <tt>TabularData</tt> instance's
     *                                    row type definition.
     */
    public Object[] calculateIndex(CompositeData value) {

        // Check value is valid
        //
        checkValueType(value);

        // Return its calculated index
        //
        return internalCalculateIndex(value).toArray();
    }




    /* *** Content information query methods *** */


    /**
     * Returns <tt>true</tt> if and only if this <tt>TabularData</tt> instance contains a <tt>CompositeData</tt> value
     * (ie a row) whose index is the specified <var>key</var>. If <var>key</var> cannot be cast to a one dimension array
     * of Object instances, this method simply returns <tt>false</tt>; otherwise it returns the the result of the call to
     * <tt>this.containsKey((Object[]) key)</tt>.
     *
     * @param  key  the index value whose presence in this <tt>TabularData</tt> instance is to be tested.
     *
     * @return  <tt>true</tt> if this <tt>TabularData</tt> indexes a row value with the specified key.
     */
    public boolean containsKey(Object key) {

        // if key is not an array of Object instances, return false
        //
        Object[] k;
        try {
            k = (Object[]) key;
        } catch (ClassCastException e) {
            return false;
        }

        return  this.containsKey(k);
    }

    /**
     * Returns <tt>true</tt> if and only if this <tt>TabularData</tt> instance contains a <tt>CompositeData</tt> value
     * (ie a row) whose index is the specified <var>key</var>. If <var>key</var> is <tt>null</tt> or does not conform to
     * this <tt>TabularData</tt> instance's <tt>TabularType</tt> definition, this method simply returns <tt>false</tt>.
     *
     * @param  key  the index value whose presence in this <tt>TabularData</tt> instance is to be tested.
     *
     * @return  <tt>true</tt> if this <tt>TabularData</tt> indexes a row value with the specified key.
     */
    public boolean containsKey(Object[] key) {

        return  ( key == null ? false : dataMap.containsKey(Arrays.asList(key)));
    }

    /**
     * Returns <tt>true</tt> if and only if this <tt>TabularData</tt> instance contains the specified
     * <tt>CompositeData</tt> value. If <var>value</var> is <tt>null</tt> or does not conform to
     * this <tt>TabularData</tt> instance's row type definition, this method simply returns <tt>false</tt>.
     *
     * @param  value  the row value whose presence in this <tt>TabularData</tt> instance is to be tested.
     *
     * @return  <tt>true</tt> if this <tt>TabularData</tt> instance contains the specified row value.
     */
    public boolean containsValue(CompositeData value) {

        return dataMap.containsValue(value);
    }

    /**
     * Returns <tt>true</tt> if and only if this <tt>TabularData</tt> instance contains the specified
     * value.
     *
     * @param  value  the row value whose presence in this <tt>TabularData</tt> instance is to be tested.
     *
     * @return  <tt>true</tt> if this <tt>TabularData</tt> instance contains the specified row value.
     */
    public boolean containsValue(Object value) {

        return dataMap.containsValue(value);
    }

    /**
     * This method simply calls <tt>get((Object[]) key)</tt>.
     *
     * @throws NullPointerException  if the <var>key</var> is <tt>null</tt>
     * @throws ClassCastException    if the <var>key</var> is not of the type <tt>Object[]</tt>
     * @throws InvalidKeyException   if the <var>key</var> does not conform to this <tt>TabularData</tt> instance's
     *                               <tt>TabularType</tt> definition
     */
    public Object get(Object key) {

        return get((Object[]) key);
    }

    /**
     * Returns the <tt>CompositeData</tt> value whose index is
     * <var>key</var>, or <tt>null</tt> if there is no value mapping
     * to <var>key</var>, in this <tt>TabularData</tt> instance.
     *
     * @param key the index of the value to get in this
     * <tt>TabularData</tt> instance; * must be valid with this
     * <tt>TabularData</tt> instance's row type definition; * must not
     * be null.
     *
     * @return the value corresponding to <var>key</var>.
     *
     * @throws NullPointerException  if the <var>key</var> is <tt>null</tt>
     * @throws InvalidKeyException   if the <var>key</var> does not conform to this <tt>TabularData</tt> instance's
     *                               <tt>TabularType</tt> type definition.
     */
    public CompositeData get(Object[] key) {

        // Check key is not null and valid with tabularType
        // (throws NullPointerException, InvalidKeyException)
        //
        checkKeyType(key);

        // Return the mapping stored in the parent HashMap
        //
        return dataMap.get(Arrays.asList(key));
    }




    /* *** Content modification operations (one element at a time) *** */


    /**
     * This method simply calls <tt>put((CompositeData) value)</tt> and
     * therefore ignores its <var>key</var> parameter which can be <tt>null</tt>.
     *
     * @param key an ignored parameter.
     * @param value the {@link CompositeData} to put.
     *
     * @return the value which is put
     *
     * @throws NullPointerException  if the <var>value</var> is <tt>null</tt>
     * @throws ClassCastException if the <var>value</var> is not of
     * the type <tt>CompositeData</tt>
     * @throws InvalidOpenTypeException if the <var>value</var> does
     * not conform to this <tt>TabularData</tt> instance's
     * <tt>TabularType</tt> definition
     * @throws KeyAlreadyExistsException if the key for the
     * <var>value</var> parameter, calculated according to this
     * <tt>TabularData</tt> instance's <tt>TabularType</tt> definition
     * already maps to an existing value
     */
    public Object put(Object key, Object value) {
        internalPut((CompositeData) value);
        return value; // should be return internalPut(...); (5090566)
    }

    public void put(CompositeData value) {
        internalPut(value);
    }

    private CompositeData internalPut(CompositeData value) {
        // Check value is not null, value's type is the same as this instance's row type,
        // and calculate the value's index according to this instance's tabularType and
        // check it is not already used for a mapping in the parent HashMap
        //
        List<?> index = checkValueAndIndex(value);

        // store the (key, value) mapping in the dataMap HashMap
        //
        return dataMap.put(index, value);
    }

    /**
     * This method simply calls <tt>remove((Object[]) key)</tt>.
     *
     * @param key an <tt>Object[]</tt> representing the key to remove.
     *
     * @return previous value associated with specified key, or <tt>null</tt>
     *         if there was no mapping for key.
     *
     * @throws NullPointerException  if the <var>key</var> is <tt>null</tt>
     * @throws ClassCastException    if the <var>key</var> is not of the type <tt>Object[]</tt>
     * @throws InvalidKeyException   if the <var>key</var> does not conform to this <tt>TabularData</tt> instance's
     *                               <tt>TabularType</tt> definition
     */
    public Object remove(Object key) {

        return remove((Object[]) key);
    }

    /**
     * Removes the <tt>CompositeData</tt> value whose index is <var>key</var> from this <tt>TabularData</tt> instance,
     * and returns the removed value, or returns <tt>null</tt> if there is no value whose index is <var>key</var>.
     *
     * @param  key  the index of the value to get in this <tt>TabularData</tt> instance;
     *              must be valid with this <tt>TabularData</tt> instance's row type definition;
     *              must not be null.
     *
     * @return previous value associated with specified key, or <tt>null</tt>
     *         if there was no mapping for key.
     *
     * @throws NullPointerException  if the <var>key</var> is <tt>null</tt>
     * @throws InvalidKeyException   if the <var>key</var> does not conform to this <tt>TabularData</tt> instance's
     *                               <tt>TabularType</tt> definition
     */
    public CompositeData remove(Object[] key) {

        // Check key is not null and valid with tabularType
        // (throws NullPointerException, InvalidKeyException)
        //
        checkKeyType(key);

        // Removes the (key, value) mapping in the parent HashMap
        //
        return dataMap.remove(Arrays.asList(key));
    }



    /* ***   Content modification bulk operations   *** */


    /**
     * Add all the values contained in the specified map <var>t</var>
     * to this <tt>TabularData</tt> instance.  This method converts
     * the collection of values contained in this map into an array of
     * <tt>CompositeData</tt> values, if possible, and then call the
     * method <tt>putAll(CompositeData[])</tt>. Note that the keys
     * used in the specified map <var>t</var> are ignored. This method
     * allows, for example to add the content of another
     * <tt>TabularData</tt> instance with the same row type (but
     * possibly different index names) into this instance.
     *
     * @param t the map whose values are to be added as new rows to
     * this <tt>TabularData</tt> instance; if <var>t</var> is
     * <tt>null</tt> or empty, this method returns without doing
     * anything.
     *
     * @throws NullPointerException if a value in <var>t</var> is
     * <tt>null</tt>.
     * @throws ClassCastException if a value in <var>t</var> is not an
     * instance of <tt>CompositeData</tt>.
     * @throws InvalidOpenTypeException if a value in <var>t</var>
     * does not conform to this <tt>TabularData</tt> instance's row
     * type definition.
     * @throws KeyAlreadyExistsException if the index for a value in
     * <var>t</var>, calculated according to this
     * <tt>TabularData</tt> instance's <tt>TabularType</tt> definition
     * already maps to an existing value in this instance, or two
     * values in <var>t</var> have the same index.
     */
    public void putAll(Map<?,?> t) {

        // if t is null or empty, just return
        //
        if ( (t == null) || (t.size() == 0) ) {
            return;
        }

        // Convert the values in t into an array of <tt>CompositeData</tt>
        //
        CompositeData[] values;
        try {
            values =
                t.values().toArray(new CompositeData[t.size()]);
        } catch (java.lang.ArrayStoreException e) {
            throw new ClassCastException("Map argument t contains values which are not instances of <tt>CompositeData</tt>");
        }

        // Add the array of values
        //
        putAll(values);
    }

    /**
     * Add all the elements in <var>values</var> to this
     * <tt>TabularData</tt> instance.  If any element in
     * <var>values</var> does not satisfy the constraints defined in
     * {@link #put(CompositeData) <tt>put</tt>}, or if any two
     * elements in <var>values</var> have the same index calculated
     * according to this <tt>TabularData</tt> instance's
     * <tt>TabularType</tt> definition, then an exception describing
     * the failure is thrown and no element of <var>values</var> is
     * added, thus leaving this <tt>TabularData</tt> instance
     * unchanged.
     *
     * @param values the array of composite data values to be added as
     * new rows to this <tt>TabularData</tt> instance; if
     * <var>values</var> is <tt>null</tt> or empty, this method
     * returns without doing anything.
     *
     * @throws NullPointerException if an element of <var>values</var>
     * is <tt>null</tt>
     * @throws InvalidOpenTypeException if an element of
     * <var>values</var> does not conform to this
     * <tt>TabularData</tt> instance's row type definition (ie its
     * <tt>TabularType</tt> definition)
     * @throws KeyAlreadyExistsException if the index for an element
     * of <var>values</var>, calculated according to this
     * <tt>TabularData</tt> instance's <tt>TabularType</tt> definition
     * already maps to an existing value in this instance, or two
     * elements of <var>values</var> have the same index
     */
    public void putAll(CompositeData[] values) {

        // if values is null or empty, just return
        //
        if ( (values == null) || (values.length == 0) ) {
            return;
        }

        // create the list of indexes corresponding to each value
        List<List<?>> indexes =
            new ArrayList<List<?>>(values.length + 1);

        // Check all elements in values and build index list
        //
        List<?> index;
        for (int i=0; i<values.length; i++) {
            // check value and calculate index
            index = checkValueAndIndex(values[i]);
            // check index is different of those previously calculated
            if (indexes.contains(index)) {
                throw new KeyAlreadyExistsException("Argument elements values["+ i +"] and values["+ indexes.indexOf(index) +
                                                    "] have the same indexes, "+
                                                    "calculated according to this TabularData instance's tabularType.");
            }
            // add to index list
            indexes.add(index);
        }

        // store all (index, value) mappings in the dataMap HashMap
        //
        for (int i=0; i<values.length; i++) {
            dataMap.put(indexes.get(i), values[i]);
        }
    }

    /**
     * Removes all rows from this <code>TabularDataSupport</code> instance.
     */
    public void clear() {

        dataMap.clear();
    }



    /* ***  Informational methods from java.util.Map  *** */

    /**
     * Returns the number of rows in this <code>TabularDataSupport</code> instance.
     *
     * @return the number of rows in this <code>TabularDataSupport</code> instance.
     */
    public int size() {

        return dataMap.size();
    }

    /**
     * Returns <tt>true</tt> if this <code>TabularDataSupport</code> instance contains no rows.
     *
     * @return <tt>true</tt> if this <code>TabularDataSupport</code> instance contains no rows.
     */
    public boolean isEmpty() {

        return (this.size() == 0);
    }



    /* ***  Collection views from java.util.Map  *** */

    /**
     * Returns a set view of the keys contained in the underlying map of this
     * {@code TabularDataSupport} instance used to index the rows.
     * Each key contained in this {@code Set} is an unmodifiable {@code List<?>}
     * so the returned set view is a {@code Set<List<?>>} but is declared as a
     * {@code Set<Object>} for compatibility reasons.
     * The set is backed by the underlying map of this
     * {@code TabularDataSupport} instance, so changes to the
     * {@code TabularDataSupport} instance are reflected in the
     * set, and vice-versa.
     *
     * The set supports element removal, which removes the corresponding
     * row from this {@code TabularDataSupport} instance, via the
     * {@link Iterator#remove}, {@link Set#remove}, {@link Set#removeAll},
     * {@link Set#retainAll}, and {@link Set#clear} operations. It does
     *  not support the {@link Set#add} or {@link Set#addAll} operations.
     *
     * @return a set view ({@code Set<List<?>>}) of the keys used to index
     * the rows of this {@code TabularDataSupport} instance.
     */
    public Set<Object> keySet() {

        return dataMap.keySet() ;
    }

    /**
     * Returns a collection view of the rows contained in this
     * {@code TabularDataSupport} instance. The returned {@code Collection}
     * is a {@code Collection<CompositeData>} but is declared as a
     * {@code Collection<Object>} for compatibility reasons.
     * The returned collection can be used to iterate over the values.
     * The collection is backed by the underlying map, so changes to the
     * {@code TabularDataSupport} instance are reflected in the collection,
     * and vice-versa.
     *
     * The collection supports element removal, which removes the corresponding
     * index to row mapping from this {@code TabularDataSupport} instance, via
     * the {@link Iterator#remove}, {@link Collection#remove},
     * {@link Collection#removeAll}, {@link Collection#retainAll},
     * and {@link Collection#clear} operations. It does not support
     * the {@link Collection#add} or {@link Collection#addAll} operations.
     *
     * @return a collection view ({@code Collection<CompositeData>}) of
     * the values contained in this {@code TabularDataSupport} instance.
     */
    @SuppressWarnings("unchecked")  // historical confusion about the return type
    public Collection<Object> values() {

        return (Collection) dataMap.values() ;
    }


    /**
     * Returns a collection view of the index to row mappings
     * contained in this {@code TabularDataSupport} instance.
     * Each element in the returned collection is
     * a {@code Map.Entry<List<?>,CompositeData>} but
     * is declared as a {@code Map.Entry<Object,Object>}
     * for compatibility reasons. Each of the map entry
     * keys is an unmodifiable {@code List<?>}.
     * The collection is backed by the underlying map of this
     * {@code TabularDataSupport} instance, so changes to the
     * {@code TabularDataSupport} instance are reflected in
     * the collection, and vice-versa.
     * The collection supports element removal, which removes
     * the corresponding mapping from the map, via the
     * {@link Iterator#remove}, {@link Collection#remove},
     * {@link Collection#removeAll}, {@link Collection#retainAll},
     * and {@link Collection#clear} operations. It does not support
     * the {@link Collection#add} or {@link Collection#addAll}
     * operations.
     * <p>
     * <b>IMPORTANT NOTICE</b>: Do not use the {@code setValue} method of the
     * {@code Map.Entry} elements contained in the returned collection view.
     * Doing so would corrupt the index to row mappings contained in this
     * {@code TabularDataSupport} instance.
     *
     * @return a collection view ({@code Set<Map.Entry<List<?>,CompositeData>>})
     * of the mappings contained in this map.
     * @see java.util.Map.Entry
     */
    @SuppressWarnings("unchecked")  // historical confusion about the return type
    public Set<Map.Entry<Object,Object>> entrySet() {

        return (Set) dataMap.entrySet();
    }


    /* ***  Commodity methods from java.lang.Object  *** */


    /**
     * Returns a clone of this <code>TabularDataSupport</code> instance:
     * the clone is obtained by calling <tt>super.clone()</tt>, and then cloning the underlying map.
     * 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.
     */
    /* We cannot use covariance here and return TabularDataSupport
       because this would fail with existing code that subclassed
       TabularDataSupport and overrode Object clone().  It would not
       override the new clone().  */
    public Object clone() {
        try {
            TabularDataSupport c = (TabularDataSupport) super.clone();
            c.dataMap = new HashMap<Object,CompositeData>(c.dataMap);
            return c;
        }
        catch (CloneNotSupportedException e) {
            throw new InternalError(e.toString());
        }
    }


    /**
     * Compares the specified <var>obj</var> parameter with this <code>TabularDataSupport</code> instance for equality.
     * <p>
     * Returns <tt>true</tt> if and only if all of the following statements are true:
     * <ul>
     * <li><var>obj</var> is non null,</li>
     * <li><var>obj</var> also implements the <code>TabularData</code> interface,</li>
     * <li>their tabular types are equal</li>
     * <li>their contents (ie all CompositeData values) are equal.</li>
     * </ul>
     * This ensures that this <tt>equals</tt> method works properly for <var>obj</var> parameters which are
     * different implementations of the <code>TabularData</code> interface.
     * <br>&nbsp;
     * @param  obj  the object to be compared for equality with this <code>TabularDataSupport</code> instance;
     *
     * @return  <code>true</code> if the specified object is equal to this <code>TabularDataSupport</code> instance.
     */
    public boolean equals(Object obj) {

        // if obj is null, return false
        //
        if (obj == null) {
            return false;
        }

        // if obj is not a TabularData, return false
        //
        TabularData other;
        try {
            other = (TabularData) obj;
        } catch (ClassCastException e) {
            return false;
        }

        // Now, really test for equality between this TabularData implementation and the other:
        //

        // their tabularType should be equal
        if ( ! this.getTabularType().equals(other.getTabularType()) ) {
            return false;
        }

        // their contents should be equal:
        // . same size
        // . values in this instance are in the other (we know there are no duplicate elements possible)
        // (row values comparison is enough, because keys are calculated according to tabularType)

        if (this.size() != other.size()) {
            return false;
        }
        for (Iterator iter = this.values().iterator(); iter.hasNext();  ) {
            CompositeData value = (CompositeData) iter.next();
            if ( ! other.containsValue(value) ) {
                return false;
            }
        }

        // All tests for equality were successfull
        //
        return true;
    }

    /**
     * Returns the hash code value for this <code>TabularDataSupport</code> instance.
     * <p>
     * The hash code of a <code>TabularDataSupport</code> instance is the sum of the hash codes
     * of all elements of information used in <code>equals</code> comparisons
     * (ie: its <i>tabular type</i> and its content, where the content is defined as all the CompositeData values).
     * <p>
     * This ensures that <code> t1.equals(t2) </code> implies that <code> t1.hashCode()==t2.hashCode() </code>
     * for any two <code>TabularDataSupport</code> instances <code>t1</code> and <code>t2</code>,
     * as required by the general contract of the method
     * {@link Object#hashCode() Object.hashCode()}.
     * <p>
     * However, note that another instance of a class implementing the <code>TabularData</code> interface
     * may be equal to this <code>TabularDataSupport</code> instance as defined by {@link #equals},
     * but may have a different hash code if it is calculated differently.
     *
     * @return  the hash code value for this <code>TabularDataSupport</code> instance
     */
   public int hashCode() {

        int result = 0;

        result += this.tabularType.hashCode();
        for (Iterator iter = this.values().iterator(); iter.hasNext();  ) {
            result += ((CompositeData)iter.next()).hashCode();
        }

        return result;

    }

    /**
     * Returns a string representation of this <code>TabularDataSupport</code> instance.
     * <p>
     * The string representation consists of the name of this class (ie <code>javax.management.openmbean.TabularDataSupport</code>),
     * the string representation of the tabular type of this instance, and the string representation of the contents
     * (ie list the key=value mappings as returned by a call to
     * <tt>dataMap.</tt>{@link java.util.HashMap#toString() toString()}).
     *
     * @return  a string representation of this <code>TabularDataSupport</code> instance
     */
    public String toString() {

        return new StringBuilder()
            .append(this.getClass().getName())
            .append("(tabularType=")
            .append(tabularType.toString())
            .append(",contents=")
            .append(dataMap.toString())
            .append(")")
            .toString();
    }




    /* *** TabularDataSupport internal utility methods *** */


    /**
     * Returns the index for value, assuming value is valid for this <tt>TabularData</tt> instance
     * (ie value is not null, and its composite type is equal to row type).
     *
     * The index is a List, and not an array, so that an index.equals(otherIndex) call will actually compare contents,
     * not just the objects references as is done for an array object.
     *
     * The returned List is unmodifiable so that once a row has been put into the dataMap, its index cannot be modified,
     * for example by a user that would attempt to modify an index contained in the Set returned by keySet().
     */
    private List<?> internalCalculateIndex(CompositeData value) {

        return Collections.unmodifiableList(Arrays.asList(value.getAll(this.indexNamesArray)));
    }

    /**
     * Checks if the specified key is valid for this <tt>TabularData</tt> instance.
     *
     * @throws  NullPointerException
     * @throws  InvalidOpenTypeException
     */
    private void checkKeyType(Object[] key) {

        // Check key is neither null nor empty
        //
        if ( (key == null) || (key.length == 0) ) {
            throw new NullPointerException("Argument key cannot be null or empty.");
        }

        /* Now check key is valid with tabularType index and row type definitions: */

        // key[] should have the size expected for an index
        //
        if (key.length != this.indexNamesArray.length) {
            throw new InvalidKeyException("Argument key's length="+ key.length +
                                          " is different from the number of item values, which is "+ indexNamesArray.length +
                                          ", specified for the indexing rows in this TabularData instance.");
        }

        // each element in key[] should be a value for its corresponding open type specified in rowType
        //
        OpenType<?> keyElementType;
        for (int i=0; i<key.length; i++) {
            keyElementType = tabularType.getRowType().getType(this.indexNamesArray[i]);
            if ( (key[i] != null) && (! keyElementType.isValue(key[i])) ) {
                throw new InvalidKeyException("Argument element key["+ i +"] is not a value for the open type expected for "+
                                              "this element of the index, whose name is \""+ indexNamesArray[i] +
                                              "\" and whose open type is "+ keyElementType);
            }
        }
    }

    /**
     * Checks the specified value's type is valid for this <tt>TabularData</tt> instance
     * (ie value is not null, and its composite type is equal to row type).
     *
     * @throws  NullPointerException
     * @throws  InvalidOpenTypeException
     */
    private void checkValueType(CompositeData value) {

        // Check value is not null
        //
        if (value == null) {
            throw new NullPointerException("Argument value cannot be null.");
        }

        // if value's type is not the same as this instance's row type, throw InvalidOpenTypeException
        //
        if (!tabularType.getRowType().isValue(value)) {
            throw new InvalidOpenTypeException("Argument value's composite type ["+ value.getCompositeType() +
                                               "] is not assignable to "+
                                               "this TabularData instance's row type ["+ tabularType.getRowType() +"].");
        }
    }

    /**
     * Checks if the specified value can be put (ie added) in this <tt>TabularData</tt> instance
     * (ie value is not null, its composite type is equal to row type, and its index is not already used),
     * and returns the index calculated for this value.
     *
     * The index is a List, and not an array, so that an index.equals(otherIndex) call will actually compare contents,
     * not just the objects references as is done for an array object.
     *
     * @throws  NullPointerException
     * @throws  InvalidOpenTypeException
     * @throws  KeyAlreadyExistsException
     */
    private List<?> checkValueAndIndex(CompositeData value) {

        // Check value is valid
        //
        checkValueType(value);

        // Calculate value's index according to this instance's tabularType
        // and check it is not already used for a mapping in the parent HashMap
        //
        List<?> index = internalCalculateIndex(value);

        if (dataMap.containsKey(index)) {
            throw new KeyAlreadyExistsException("Argument value's index, calculated according to this TabularData "+
                                                "instance's tabularType, already refers to a value in this table.");
        }

        // The check is OK, so return the index
        //
        return index;
    }

    /**
     * Deserializes a {@link TabularDataSupport} from an {@link ObjectInputStream}.
     */
    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {
      in.defaultReadObject();
      List<String> tmpNames = tabularType.getIndexNames();
      indexNamesArray = tmpNames.toArray(new String[tmpNames.size()]);
    }
}