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