src/java.management/share/classes/javax/management/openmbean/TabularDataSupport.java
changeset 47216 71c04702a3d5
parent 32034 05676cfd40b5
child 49794 274be83ad4b7
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     1 /*
       
     2  * Copyright (c) 2000, 2011, 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 
       
    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>,
       
    77                Cloneable, Serializable {
       
    78 
       
    79 
       
    80     /* Serial version */
       
    81     static final long serialVersionUID = 5720150593236309827L;
       
    82 
       
    83 
       
    84     /**
       
    85      * @serial This tabular data instance's contents: a {@link HashMap}
       
    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         //
       
   148         this.tabularType = tabularType;
       
   149         List<String> tmpNames = tabularType.getIndexNames();
       
   150         this.indexNamesArray = tmpNames.toArray(new String[tmpNames.size()]);
       
   151 
       
   152         // Since LinkedHashMap was introduced in SE 1.4, it's conceivable even
       
   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.
       
   583      *
       
   584      * The set supports element removal, which removes the corresponding
       
   585      * row from this {@code TabularDataSupport} instance, via the
       
   586      * {@link Iterator#remove}, {@link Set#remove}, {@link Set#removeAll},
       
   587      * {@link Set#retainAll}, and {@link Set#clear} operations. It does
       
   588      *  not support the {@link Set#add} or {@link Set#addAll} operations.
       
   589      *
       
   590      * @return a set view ({@code Set<List<?>>}) of the keys used to index
       
   591      * the rows of this {@code TabularDataSupport} instance.
       
   592      */
       
   593     public Set<Object> keySet() {
       
   594 
       
   595         return dataMap.keySet() ;
       
   596     }
       
   597 
       
   598     /**
       
   599      * Returns a collection view of the rows contained in this
       
   600      * {@code TabularDataSupport} instance. The returned {@code Collection}
       
   601      * is a {@code Collection<CompositeData>} but is declared as a
       
   602      * {@code Collection<Object>} for compatibility reasons.
       
   603      * The returned collection can be used to iterate over the values.
       
   604      * The collection is backed by the underlying map, so changes to the
       
   605      * {@code TabularDataSupport} instance are reflected in the collection,
       
   606      * and vice-versa.
       
   607      *
       
   608      * The collection supports element removal, which removes the corresponding
       
   609      * index to row mapping from this {@code TabularDataSupport} instance, via
       
   610      * the {@link Iterator#remove}, {@link Collection#remove},
       
   611      * {@link Collection#removeAll}, {@link Collection#retainAll},
       
   612      * and {@link Collection#clear} operations. It does not support
       
   613      * the {@link Collection#add} or {@link Collection#addAll} operations.
       
   614      *
       
   615      * @return a collection view ({@code Collection<CompositeData>}) of
       
   616      * the values contained in this {@code TabularDataSupport} instance.
       
   617      */
       
   618     @SuppressWarnings("unchecked")  // historical confusion about the return type
       
   619     public Collection<Object> values() {
       
   620 
       
   621         return Util.cast(dataMap.values());
       
   622     }
       
   623 
       
   624 
       
   625     /**
       
   626      * Returns a collection view of the index to row mappings
       
   627      * contained in this {@code TabularDataSupport} instance.
       
   628      * Each element in the returned collection is
       
   629      * a {@code Map.Entry<List<?>,CompositeData>} but
       
   630      * is declared as a {@code Map.Entry<Object,Object>}
       
   631      * for compatibility reasons. Each of the map entry
       
   632      * keys is an unmodifiable {@code List<?>}.
       
   633      * The collection is backed by the underlying map of this
       
   634      * {@code TabularDataSupport} instance, so changes to the
       
   635      * {@code TabularDataSupport} instance are reflected in
       
   636      * the collection, and vice-versa.
       
   637      * The collection supports element removal, which removes
       
   638      * the corresponding mapping from the map, via the
       
   639      * {@link Iterator#remove}, {@link Collection#remove},
       
   640      * {@link Collection#removeAll}, {@link Collection#retainAll},
       
   641      * and {@link Collection#clear} operations. It does not support
       
   642      * the {@link Collection#add} or {@link Collection#addAll}
       
   643      * operations.
       
   644      * <p>
       
   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>&nbsp;
       
   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         //
       
   722 
       
   723         // their tabularType should be equal
       
   724         if ( ! this.getTabularType().equals(other.getTabularType()) ) {
       
   725             return false;
       
   726         }
       
   727 
       
   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);
       
   907 
       
   908         if (dataMap.containsKey(index)) {
       
   909             throw new KeyAlreadyExistsException("Argument value's index, calculated according to this TabularData "+
       
   910                                                 "instance's tabularType, already refers to a value in this table.");
       
   911         }
       
   912 
       
   913         // The check is OK, so return the index
       
   914         //
       
   915         return index;
       
   916     }
       
   917 
       
   918     /**
       
   919      * Deserializes a {@link TabularDataSupport} from an {@link ObjectInputStream}.
       
   920      */
       
   921     private void readObject(ObjectInputStream in)
       
   922             throws IOException, ClassNotFoundException {
       
   923       in.defaultReadObject();
       
   924       List<String> tmpNames = tabularType.getIndexNames();
       
   925       indexNamesArray = tmpNames.toArray(new String[tmpNames.size()]);
       
   926     }
       
   927 }