/* * @(#)TabularDataSupport.java 3.30 03/12/19 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package javax.management.openmbean; // java import // import java.io.IOException; import java.io.Serializable; import java.io.ObjectInputStream; import java.util.Iterator; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.Collection; import java.util.Collections; import java.util.Arrays; import java.util.ArrayList; import java.util.List; // jmx import // /** * The TabularDataSupport class is the open data class which implements the TabularData * and the Map interfaces, and which is internally based on a hash map data structure. * * @version 3.30 03/12/19 * @author Sun Microsystems, Inc. * * @since 1.5 * @since.unbundled JMX 1.1 */ public class TabularDataSupport implements TabularData, Map, Cloneable, Serializable { /* Serial version */ static final long serialVersionUID = 5720150593236309827L; /** * @serial This tabular data instance's contents: a {@link HashMap} */ private Map dataMap; /** * @serial This tabular data instance's tabular type */ private TabularType tabularType; /** * @serial The array of item names that define the index used for rows (convenience field) */ private transient String[] indexNamesArray; /* *** Constructors *** */ /** * Creates an empty TabularDataSupport instance whose open-type is tabularType, * and whose underlying HashMap has a default initial capacity (101) and default load factor (0.75). *

* This constructor simply calls this(tabularType, 101, 0.75f); * * @param tabularType the tabular type describing this TabularData instance; * cannot be null. * * @throws IllegalArgumentException if the tabular type is null. */ public TabularDataSupport(TabularType tabularType) { this(tabularType, 101, 0.75f); } /** * Creates an empty TabularDataSupport instance whose open-type is tabularType, * and whose underlying HashMap has the specified initial capacity and load factor. * * @param tabularType the tabular type describing this TabularData instance; * cannot be null. * * @param initialCapacity the initial capacity of the HashMap. * * @param loadFactor the load factor of the HashMap * * @throws IllegalArgumentException if the initial capacity is less than zero, * or the load factor is nonpositive, * or the tabular type is null. */ public TabularDataSupport(TabularType tabularType, int initialCapacity, float loadFactor) { // Check tabularType is not null // if (tabularType == null) { throw new IllegalArgumentException("Argument tabularType cannot be null."); } // Initialize this.tabularType (and indexNamesArray for convenience) // this.tabularType = tabularType; List tmpNames = tabularType.getIndexNames(); this.indexNamesArray = (String[]) tmpNames.toArray(new String[tmpNames.size()]); // Construct the empty contents HashMap // this.dataMap = new HashMap(initialCapacity, loadFactor); } /* *** TabularData specific information methods *** */ /** * Returns the tabular type describing this TabularData instance. */ public TabularType getTabularType() { return tabularType; } /** * Calculates the index that would be used in this TabularData instance to refer to the specified * composite data value parameter if it were added to this instance. * This method checks for the type validity of the specified value, * but does not check if the calculated index is already used to refer to a value in this TabularData instance. * * @param value the composite data value whose index in this * TabularData instance is to be calculated; * must be of the same composite type as this instance's row type; * must not be null. * * @return the index that the specified value would have in this TabularData instance. * * @throws NullPointerException if value is null. * * @throws InvalidOpenTypeException if value does not conform to this TabularData instance's * row type definition. */ public Object[] calculateIndex(CompositeData value) { // Check value is valid // checkValueType(value); // Return its calculated index // return internalCalculateIndex(value).toArray(); } /* *** Content information query methods *** */ /** * Returns true if and only if this TabularData instance contains a CompositeData value * (ie a row) whose index is the specified key. If key cannot be cast to a one dimension array * of Object instances, this method simply returns false; otherwise it returns the the result of the call to * this.containsKey((Object[]) key). * * @param key the index value whose presence in this TabularData instance is to be tested. * * @return true if this TabularData indexes a row value with the specified key. */ public boolean containsKey(Object key) { // if key is not an array of Object instances, return false // Object[] k; try { k = (Object[]) key; } catch (ClassCastException e) { return false; } return this.containsKey(k); } /** * Returns true if and only if this TabularData instance contains a CompositeData value * (ie a row) whose index is the specified key. If key is null or does not conform to * this TabularData instance's TabularType definition, this method simply returns false. * * @param key the index value whose presence in this TabularData instance is to be tested. * * @return true if this TabularData indexes a row value with the specified key. */ public boolean containsKey(Object[] key) { return ( key == null ? false : dataMap.containsKey(Arrays.asList(key)) ); } /** * Returns true if and only if this TabularData instance contains the specified * CompositeData value. If value is null or does not conform to * this TabularData instance's row type definition, this method simply returns false. * * @param value the row value whose presence in this TabularData instance is to be tested. * * @return true if this TabularData instance contains the specified row value. */ public boolean containsValue(CompositeData value) { return dataMap.containsValue(value); } /** * Returns true if and only if this TabularData instance contains the specified * value. * * @param value the row value whose presence in this TabularData instance is to be tested. * * @return true if this TabularData instance contains the specified row value. */ public boolean containsValue(Object value) { return dataMap.containsValue(value); } /** * This method simply calls get((Object[]) key). * * @throws NullPointerException if the key is null * @throws ClassCastException if the key is not of the type Object[] * @throws InvalidKeyException if the key does not conform to this TabularData instance's * TabularType definition */ public Object get(Object key) { return get((Object[]) key); } /** * Returns the CompositeData value whose index is * key, or null if there is no value mapping * to key, in this TabularData instance. * * @param key the index of the value to get in this * TabularData instance; * must be valid with this * TabularData instance's row type definition; * must not * be null. * * @return the value corresponding to key. * * @throws NullPointerException if the key is null * @throws InvalidKeyException if the key does not conform to this TabularData instance's * TabularType type definition. */ public CompositeData get(Object[] key) { // Check key is not null and valid with tabularType // (throws NullPointerException, InvalidKeyException) // checkKeyType(key); // Return the mapping stored in the parent HashMap // return (CompositeData) dataMap.get(Arrays.asList(key)); } /* *** Content modification operations (one element at a time) *** */ /** * This method simply calls put((CompositeData) value) and * therefore ignores its key parameter which can be null. * * @param key an ignored parameter. * @param value the {@link CompositeData} to put. * * @return the value which is put * * @throws NullPointerException if the value is null * @throws ClassCastException if the value is not of the type CompositeData * @throws InvalidOpenTypeException if the value does not conform to this TabularData instance's * TabularType definition * @throws KeyAlreadyExistsException if the key for the value parameter, calculated according to * this TabularData instance's TabularType definition * already maps to an existing value */ public Object put(Object key, Object value) { put((CompositeData) value); return value; } public void put(CompositeData value) { // Check value is not null, value's type is the same as this instance's row type, // and calculate the value's index according to this instance's tabularType and // check it is not already used for a mapping in the parent HashMap // List index = checkValueAndIndex(value); // store the (key, value) mapping in the dataMap HashMap // dataMap.put(index, value); } /** * This method simply calls remove((Object[]) key). * * @param key an Object[] representing the key to remove. * * @return previous value associated with specified key, or null * if there was no mapping for key. * * @throws NullPointerException if the key is null * @throws ClassCastException if the key is not of the type Object[] * @throws InvalidKeyException if the key does not conform to this TabularData instance's * TabularType definition */ public Object remove(Object key) { return remove((Object[]) key); } /** * Removes the CompositeData value whose index is key from this TabularData instance, * and returns the removed value, or returns null if there is no value whose index is key. * * @param key the index of the value to get in this TabularData instance; * must be valid with this TabularData instance's row type definition; * must not be null. * * @return previous value associated with specified key, or null * if there was no mapping for key. * * @throws NullPointerException if the key is null * @throws InvalidKeyException if the key does not conform to this TabularData instance's * TabularType definition */ public CompositeData remove(Object[] key) { // Check key is not null and valid with tabularType // (throws NullPointerException, InvalidKeyException) // checkKeyType(key); // Removes the (key, value) mapping in the parent HashMap // return (CompositeData) dataMap.remove(Arrays.asList(key)); } /* *** Content modification bulk operations *** */ /** * Add all the values contained in the specified map t to this TabularData instance. * This method converts the collection of values contained in this map into an array of CompositeData values, * if possible, and then call the method putAll(CompositeData[]). Note that the keys used in the specified * map t are ignored. This method allows, for example to add the content of another TabularData * instance with the same row type (but possibly different index names) into this instance. * * @param t the map whose values are to be added as new rows to this TabularData instance; * if t is null or empty, this method returns without doing anything. * * @throws NullPointerException if a value in t is null. * @throws ClassCastException if a value in t is not an instance of CompositeData. * @throws InvalidOpenTypeException if a value in t does not conform to * this TabularData instance's row type definition. * @throws KeyAlreadyExistsException if the index for a value in t, calculated according to * this TabularData instance's TabularType definition * already maps to an existing value in this instance, * or two values in t have the same index. */ public void putAll(Map t) { // if t is null or empty, just return // if ( (t == null) || (t.size() == 0) ) { return; } // Convert the values in t into an array of CompositeData // CompositeData[] values; try { values = (CompositeData[]) t.values().toArray(new CompositeData[t.size()]); } catch (java.lang.ArrayStoreException e) { throw new ClassCastException("Map argument t contains values which are not instances of CompositeData"); } // Add the array of values // putAll(values); } /** * Add all the elements in values to this TabularData instance. * If any element in values does not satisfy the constraints defined in {@link #put(CompositeData) put}, * or if any two elements in values have the same index calculated according to this TabularData * instance's TabularType definition, then an exception describing the failure is thrown * and no element of values is added, thus leaving this TabularData instance unchanged. * * @param values the array of composite data values to be added as new rows to this TabularData instance; * if values is null or empty, this method returns without doing anything. * * @throws NullPointerException if an element of values is null * @throws InvalidOpenTypeException if an element of values does not conform to * this TabularData instance's row type definition * (ie its TabularType definition) * @throws KeyAlreadyExistsException if the index for an element of values, calculated according to * this TabularData instance's TabularType definition * already maps to an existing value in this instance, * or two elements of values have the same index */ public void putAll(CompositeData[] values) { // if values is null or empty, just return // if ( (values == null) || (values.length == 0) ) { return; } // create the list of indexes corresponding to each value ArrayList indexes = new ArrayList(values.length + 1); // Check all elements in values and build index list // List index; for (int i=0; iTabularDataSupport instance. */ public void clear() { dataMap.clear(); } /* *** Informational methods from java.util.Map *** */ /** * Returns the number of rows in this TabularDataSupport instance. * * @return the number of rows in this TabularDataSupport instance. */ public int size() { return dataMap.size(); } /** * Returns true if this TabularDataSupport instance contains no rows. * * @return true if this TabularDataSupport instance contains no rows. */ public boolean isEmpty() { return (this.size() == 0); } /* *** Collection views from java.util.Map *** */ /** * Returns a set view of the keys contained in the underlying map of this TabularDataSupport instance, * and used to index the rows. Each key contained in this set is an unmodifiable List. * The set is backed by the underlying map of this TabularDataSupport instance, * so changes to the TabularDataSupport instance are reflected in the set, and vice-versa. * * The set supports element removal, which removes the * corresponding row from this TabularDataSupport instance, via the Iterator.remove, * Set.remove, removeAll, retainAll, and * clear operations. * It does not support the add or addAll operations * * @return a set view of the keys used to index the rows of this TabularDataSupport instance. */ public Set keySet() { return dataMap.keySet() ; } /** * Returns a collection view of the rows contained in this TabularDataSupport instance. * The collection is backed by the underlying map, so changes to the TabularDataSupport instance * are reflected in the collection, and vice-versa. * * The collection supports element removal, * which removes the corresponding index to row mapping from this TabularDataSupport instance, * via the Iterator.remove, Collection.remove, * removeAll, retainAll, and clear operations. * It does not support the add or addAll operations. * * @return a collection view of the values contained in this TabularDataSupport instance. */ public Collection values() { return dataMap.values() ; } /** * Returns a collection view of the index to row mappings contained in this TabularDataSupport instance. * Each element in the returned collection is a Map.Entry. * The collection is backed by the underlying map of this TabularDataSupport instance, in * so changes to the TabularDataSupport instance are reflected the collection, and vice-versa. * The collection supports element removal, which removes the corresponding mapping from the map, via the * Iterator.remove, Collection.remove, * removeAll, retainAll, and clear operations. * It does not support the add or addAll operations. *

* IMPORTANT NOTICE: Do not use the SetValue method of Map.Entry elements contained in the returned * collection view. Doing so would corrupt the index to row mappings contained in this TabularDataSupport instance. *

* * @return a collection view of the mappings contained in this map. * @see java.util.Map.Entry */ public Set entrySet() { return dataMap.entrySet(); } /* *** Commodity methods from java.lang.Object *** */ /** * Returns a clone of this TabularDataSupport instance: * the clone is obtained by calling super.clone(), and then cloning the underlying map. * Only a shallow clone of the underlying map is made, i.e. no cloning of the indexes and row values is made as they are immutable. */ public Object clone() { try { TabularDataSupport c = (TabularDataSupport) super.clone(); c.dataMap = (HashMap) ((HashMap) c.dataMap).clone(); return c; } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } } /** * Compares the specified obj parameter with this TabularDataSupport instance for equality. *

* Returns true if and only if all of the following statements are true: *

* This ensures that this equals method works properly for obj parameters which are * different implementations of the TabularData interface. *
  * @param obj the object to be compared for equality with this TabularDataSupport instance; * * @return true if the specified object is equal to this TabularDataSupport instance. */ public boolean equals(Object obj) { // if obj is null, return false // if (obj == null) { return false; } // if obj is not a TabularData, return false // TabularData other; try { other = (TabularData) obj; } catch (ClassCastException e) { return false; } // Now, really test for equality between this TabularData implementation and the other: // // their tabularType should be equal if ( ! this.getTabularType().equals(other.getTabularType()) ) { return false; } // their contents should be equal: // . same size // . values in this instance are in the other (we know there are no duplicate elements possible) // (row values comparison is enough, because keys are calculated according to tabularType) if (this.size() != other.size()) { return false; } for (Iterator iter = this.values().iterator(); iter.hasNext(); ) { CompositeData value = (CompositeData) iter.next(); if ( ! other.containsValue(value) ) { return false; } } // All tests for equality were successfull // return true; } /** * Returns the hash code value for this TabularDataSupport instance. *

* The hash code of a TabularDataSupport instance is the sum of the hash codes * of all elements of information used in equals comparisons * (ie: its tabular type and its content, where the content is defined as all the CompositeData values). *

* This ensures that t1.equals(t2) implies that t1.hashCode()==t2.hashCode() * for any two TabularDataSupport instances t1 and t2, * as required by the general contract of the method * {@link Object#hashCode() Object.hashCode()}. *

* However, note that another instance of a class implementing the TabularData interface * may be equal to this TabularDataSupport instance as defined by {@link #equals}, * but may have a different hash code if it is calculated differently. * * @return the hash code value for this TabularDataSupport instance */ public int hashCode() { int result = 0; result += this.tabularType.hashCode(); for (Iterator iter = this.values().iterator(); iter.hasNext(); ) { result += ((CompositeData)iter.next()).hashCode(); } return result; } /** * Returns a string representation of this TabularDataSupport instance. *

* The string representation consists of the name of this class (ie javax.management.openmbean.TabularDataSupport), * the string representation of the tabular type of this instance, and the string representation of the contents * (ie list the key=value mappings as returned by a call to * dataMap.{@link java.util.HashMap#toString() toString()}). * * @return a string representation of this TabularDataSupport instance */ public String toString() { return new StringBuffer() .append(this.getClass().getName()) .append("(tabularType=") .append(tabularType.toString()) .append(",contents=") .append(dataMap.toString()) .append(")") .toString(); } /* *** TabularDataSupport internal utility methods *** */ /** * Returns the index for value, assuming value is valid for this TabularData instance * (ie value is not null, and its composite type is equal to row type). * * The index is a List, and not an array, so that an index.equals(otherIndex) call will actually compare contents, * not just the objects references as is done for an array object. * * The returned List is unmodifiable so that once a row has been put into the dataMap, its index cannot be modified, * for example by a user that would attempt to modify an index contained in the Set returned by keySet(). */ private List internalCalculateIndex(CompositeData value) { return Collections.unmodifiableList(Arrays.asList(value.getAll(this.indexNamesArray))); } /** * Checks if the specified key is valid for this TabularData instance. * * @throws NullPointerException * @throws InvalidOpenTypeException */ private void checkKeyType(Object[] key) { // Check key is neither null nor empty // if ( (key == null) || (key.length == 0) ) { throw new NullPointerException("Argument key cannot be null or empty."); } /* Now check key is valid with tabularType index and row type definitions: */ // key[] should have the size expected for an index // if (key.length != this.indexNamesArray.length) { throw new InvalidKeyException("Argument key's length="+ key.length + " is different from the number of item values, which is "+ indexNamesArray.length + ", specified for the indexing rows in this TabularData instance."); } // each element in key[] should be a value for its corresponding open type specified in rowType // OpenType keyElementType; for (int i=0; iTabularData instance * (ie value is not null, and its composite type is equal to row type). * * @throws NullPointerException * @throws InvalidOpenTypeException */ private void checkValueType(CompositeData value) { // Check value is not null // if (value == null) { throw new NullPointerException("Argument value cannot be null."); } // if value's type is not the same as this instance's row type, throw InvalidOpenTypeException // if ( ! value.getCompositeType().equals(tabularType.getRowType()) ) { throw new InvalidOpenTypeException("Argument value's composite type ["+ value.getCompositeType() + "] is not equal to "+ "this TabularData instance's row type ["+ tabularType.getRowType() +"]."); } } /** * Checks if the specified value can be put (ie added) in this TabularData instance * (ie value is not null, its composite type is equal to row type, and its index is not already used), * and returns the index calculated for this value. * * The index is a List, and not an array, so that an index.equals(otherIndex) call will actually compare contents, * not just the objects references as is done for an array object. * * @throws NullPointerException * @throws InvalidOpenTypeException * @throws KeyAlreadyExistsException */ private List checkValueAndIndex(CompositeData value) { // Check value is valid // checkValueType(value); // Calculate value's index according to this instance's tabularType // and check it is not already used for a mapping in the parent HashMap // List index = internalCalculateIndex(value); if (dataMap.containsKey(index)) { throw new KeyAlreadyExistsException("Argument value's index, calculated according to this TabularData "+ "instance's tabularType, already refers to a value in this table."); } // The check is OK, so return the index // return index; } /** * Deserializes a {@link TabularDataSupport} from an {@link ObjectInputStream}. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); List tmpNames = tabularType.getIndexNames(); indexNamesArray = (String[]) tmpNames.toArray(new String[tmpNames.size()]); } }