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