1 /*
   2  * Copyright (c) 2000, 2014, 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 java.util.Set;
  33 import java.util.TreeMap;
  34 import java.util.Collections;
  35 import java.util.Iterator;
  36 
  37 // jmx import
  38 //
  39 
  40 
  41 /**
  42  * The <code>CompositeType</code> class is the <i>open type</i> class
  43  * whose instances describe the types of {@link CompositeData CompositeData} values.
  44  *
  45  *
  46  * @since 1.5
  47  */
  48 public class CompositeType extends OpenType<CompositeData> {
  49 
  50     /* Serial version */
  51     static final long serialVersionUID = -5366242454346948798L;
  52 
  53     /**
  54      * @serial Sorted mapping of the item names to their descriptions
  55      */
  56     private TreeMap<String,String> nameToDescription;
  57 
  58     /**
  59      * @serial Sorted mapping of the item names to their open types
  60      */
  61     private TreeMap<String,OpenType<?>> nameToType;
  62 
  63     /* As this instance is immutable, following three values need only
  64      * be calculated once.  */
  65     private transient Integer myHashCode = null;
  66     private transient String  myToString = null;
  67     private transient Set<String> myNamesSet = null;
  68 
  69 
  70     /* *** Constructor *** */
  71 
  72     /**
  73      * Constructs a <code>CompositeType</code> instance, checking for the validity of the given parameters.
  74      * The validity constraints are described below for each parameter.
  75      * <p>
  76      * Note that the contents of the three array parameters
  77      * <var>itemNames</var>, <var>itemDescriptions</var> and <var>itemTypes</var>
  78      * are internally copied so that any subsequent modification of these arrays by the caller of this constructor
  79      * has no impact on the constructed <code>CompositeType</code> instance.
  80      * <p>
  81      * The Java class name of composite data values this composite type represents
  82      * (ie the class name returned by the {@link OpenType#getClassName() getClassName} method)
  83      * is set to the string value returned by <code>CompositeData.class.getName()</code>.
  84      *
  85      * @param  typeName  The name given to the composite type this instance represents; cannot be a null or empty string.
  86      *
  87      * @param  description  The human readable description of the composite type this instance represents;
  88      *                      cannot be a null or empty string.
  89      *
  90      * @param  itemNames  The names of the items contained in the
  91      *                    composite data values described by this <code>CompositeType</code> instance;
  92      *                    cannot be null and should contain at least one element; no element can be a null or empty string.
  93      *                    Note that the order in which the item names are given is not important to differentiate a
  94      *                    <code>CompositeType</code> instance from another;
  95      *                    the item names are internally stored sorted in ascending alphanumeric order.
  96      *
  97      * @param  itemDescriptions  The descriptions, in the same order as <var>itemNames</var>, of the items contained in the
  98      *                           composite data values described by this <code>CompositeType</code> instance;
  99      *                           should be of the same size as <var>itemNames</var>;
 100      *                           no element can be null or an empty string.
 101      *
 102      * @param  itemTypes  The open type instances, in the same order as <var>itemNames</var>, describing the items contained
 103      *                    in the composite data values described by this <code>CompositeType</code> instance;
 104      *                    should be of the same size as <var>itemNames</var>;
 105      *                    no element can be null.
 106      *
 107      * @throws IllegalArgumentException  If <var>typeName</var> or <var>description</var> is a null or empty string,
 108      *                                   or <var>itemNames</var> or <var>itemDescriptions</var> or <var>itemTypes</var> is null,
 109      *                                   or any element of <var>itemNames</var> or <var>itemDescriptions</var>
 110      *                                   is a null or empty string,
 111      *                                   or any element of <var>itemTypes</var> is null,
 112      *                                   or <var>itemNames</var> or <var>itemDescriptions</var> or <var>itemTypes</var>
 113      *                                   are not of the same size.
 114      *
 115      * @throws OpenDataException  If <var>itemNames</var> contains duplicate item names
 116      *                            (case sensitive, but leading and trailing whitespaces removed).
 117      */
 118     public CompositeType(String        typeName,
 119                          String        description,
 120                          String[]      itemNames,
 121                          String[]      itemDescriptions,
 122                          OpenType<?>[] itemTypes) throws OpenDataException {
 123 
 124         // Check and construct state defined by parent
 125         //
 126         super(CompositeData.class.getName(), typeName, description, false);
 127 
 128         // Check the 3 arrays are not null or empty (ie length==0) and that there is no null element or empty string in them
 129         //
 130         checkForNullElement(itemNames, "itemNames");
 131         checkForNullElement(itemDescriptions, "itemDescriptions");
 132         checkForNullElement(itemTypes, "itemTypes");
 133         checkForEmptyString(itemNames, "itemNames");
 134         checkForEmptyString(itemDescriptions, "itemDescriptions");
 135 
 136         // Check the sizes of the 3 arrays are the same
 137         //
 138         if ( (itemNames.length != itemDescriptions.length) || (itemNames.length != itemTypes.length) ) {
 139             throw new IllegalArgumentException("Array arguments itemNames[], itemDescriptions[] and itemTypes[] "+
 140                                                "should be of same length (got "+ itemNames.length +", "+
 141                                                itemDescriptions.length +" and "+ itemTypes.length +").");
 142         }
 143 
 144         // Initialize internal "names to descriptions" and "names to types" sorted maps,
 145         // and, by doing so, check there are no duplicate item names
 146         //
 147         nameToDescription = new TreeMap<String,String>();
 148         nameToType        = new TreeMap<String,OpenType<?>>();
 149         String key;
 150         for (int i=0; i<itemNames.length; i++) {
 151             key = itemNames[i].trim();
 152             if (nameToDescription.containsKey(key)) {
 153                 throw new OpenDataException("Argument's element itemNames["+ i +"]=\""+ itemNames[i] +
 154                                             "\" duplicates a previous item names.");
 155             }
 156             nameToDescription.put(key, itemDescriptions[i].trim());
 157             nameToType.put(key, itemTypes[i]);
 158         }
 159     }
 160 
 161     private static void checkForNullElement(Object[] arg, String argName) {
 162         if ( (arg == null) || (arg.length == 0) ) {
 163             throw new IllegalArgumentException("Argument "+ argName +"[] cannot be null or empty.");
 164         }
 165         for (int i=0; i<arg.length; i++) {
 166             if (arg[i] == null) {
 167                 throw new IllegalArgumentException("Argument's element "+ argName +"["+ i +"] cannot be null.");
 168             }
 169         }
 170     }
 171 
 172     private static void checkForEmptyString(String[] arg, String argName) {
 173         for (int i=0; i<arg.length; i++) {
 174             if (arg[i].trim().equals("")) {
 175                 throw new IllegalArgumentException("Argument's element "+ argName +"["+ i +"] cannot be an empty string.");
 176             }
 177         }
 178     }
 179 
 180     /* *** Composite type specific information methods *** */
 181 
 182     /**
 183      * Returns <code>true</code> if this <code>CompositeType</code> instance defines an item
 184      * whose name is <var>itemName</var>.
 185      *
 186      * @param itemName the name of the item.
 187      *
 188      * @return true if an item of this name is present.
 189      */
 190     public boolean containsKey(String itemName) {
 191 
 192         if (itemName == null) {
 193             return false;
 194         }
 195         return nameToDescription.containsKey(itemName);
 196     }
 197 
 198     /**
 199      * Returns the description of the item whose name is <var>itemName</var>,
 200      * or <code>null</code> if this <code>CompositeType</code> instance does not define any item
 201      * whose name is <var>itemName</var>.
 202      *
 203      * @param itemName the name of the item.
 204      *
 205      * @return the description.
 206      */
 207     public String getDescription(String itemName) {
 208 
 209         if (itemName == null) {
 210             return null;
 211         }
 212         return nameToDescription.get(itemName);
 213     }
 214 
 215     /**
 216      * Returns the <i>open type</i> of the item whose name is <var>itemName</var>,
 217      * or <code>null</code> if this <code>CompositeType</code> instance does not define any item
 218      * whose name is <var>itemName</var>.
 219      *
 220      * @param itemName the name of the time.
 221      *
 222      * @return the type.
 223      */
 224     public OpenType<?> getType(String itemName) {
 225 
 226         if (itemName == null) {
 227             return null;
 228         }
 229         return (OpenType<?>) nameToType.get(itemName);
 230     }
 231 
 232     /**
 233      * Returns an unmodifiable Set view of all the item names defined by this <code>CompositeType</code> instance.
 234      * The set's iterator will return the item names in ascending order.
 235      *
 236      * @return a {@link Set} of {@link String}.
 237      */
 238     public Set<String> keySet() {
 239 
 240         // Initializes myNamesSet on first call
 241         if (myNamesSet == null) {
 242             myNamesSet = Collections.unmodifiableSet(nameToDescription.keySet());
 243         }
 244 
 245         return myNamesSet; // always return the same value
 246     }
 247 
 248 
 249     /**
 250      * Tests whether <var>obj</var> is a value which could be
 251      * described by this <code>CompositeType</code> instance.
 252      *
 253      * <p>If <var>obj</var> is null or is not an instance of
 254      * <code>javax.management.openmbean.CompositeData</code>,
 255      * <code>isValue</code> returns <code>false</code>.</p>
 256      *
 257      * <p>If <var>obj</var> is an instance of
 258      * <code>javax.management.openmbean.CompositeData</code>, then let
 259      * {@code ct} be its {@code CompositeType} as returned by {@link
 260      * CompositeData#getCompositeType()}.  The result is true if
 261      * {@code this} is <em>assignable from</em> {@code ct}.  This
 262      * means that:</p>
 263      *
 264      * <ul>
 265      * <li>{@link #getTypeName() this.getTypeName()} equals
 266      * {@code ct.getTypeName()}, and
 267      * <li>there are no item names present in {@code this} that are
 268      * not also present in {@code ct}, and
 269      * <li>for every item in {@code this}, its type is assignable from
 270      * the type of the corresponding item in {@code ct}.
 271      * </ul>
 272      *
 273      * <p>A {@code TabularType} is assignable from another {@code
 274      * TabularType} if they have the same {@linkplain
 275      * TabularType#getTypeName() typeName} and {@linkplain
 276      * TabularType#getIndexNames() index name list}, and the
 277      * {@linkplain TabularType#getRowType() row type} of the first is
 278      * assignable from the row type of the second.
 279      *
 280      * <p>An {@code ArrayType} is assignable from another {@code
 281      * ArrayType} if they have the same {@linkplain
 282      * ArrayType#getDimension() dimension}; and both are {@linkplain
 283      * ArrayType#isPrimitiveArray() primitive arrays} or neither is;
 284      * and the {@linkplain ArrayType#getElementOpenType() element
 285      * type} of the first is assignable from the element type of the
 286      * second.
 287      *
 288      * <p>In every other case, an {@code OpenType} is assignable from
 289      * another {@code OpenType} only if they are equal.</p>
 290      *
 291      * <p>These rules mean that extra items can be added to a {@code
 292      * CompositeData} without making it invalid for a {@code CompositeType}
 293      * that does not have those items.</p>
 294      *
 295      * @param  obj  the value whose open type is to be tested for compatibility
 296      * with this <code>CompositeType</code> instance.
 297      *
 298      * @return <code>true</code> if <var>obj</var> is a value for this
 299      * composite type, <code>false</code> otherwise.
 300      */
 301     public boolean isValue(Object obj) {
 302 
 303         // if obj is null or not CompositeData, return false
 304         //
 305         if (!(obj instanceof CompositeData)) {
 306             return false;
 307         }
 308 
 309         // if obj is not a CompositeData, return false
 310         //
 311         CompositeData value = (CompositeData) obj;
 312 
 313         // test value's CompositeType is assignable to this CompositeType instance
 314         //
 315         CompositeType valueType = value.getCompositeType();
 316         return this.isAssignableFrom(valueType);
 317     }
 318 
 319     /**
 320      * Tests whether values of the given type can be assigned to this
 321      * open type.  The result is true if the given type is also a
 322      * CompositeType with the same name ({@link #getTypeName()}), and
 323      * every item in this type is also present in the given type with
 324      * the same name and assignable type.  There can be additional
 325      * items in the given type, which are ignored.
 326      *
 327      * @param ot the type to be tested.
 328      *
 329      * @return true if {@code ot} is assignable to this open type.
 330      */
 331     @Override
 332     boolean isAssignableFrom(OpenType<?> ot) {
 333         if (!(ot instanceof CompositeType))
 334             return false;
 335         CompositeType ct = (CompositeType) ot;
 336         if (!ct.getTypeName().equals(getTypeName()))
 337             return false;
 338         for (String key : keySet()) {
 339             OpenType<?> otItemType = ct.getType(key);
 340             OpenType<?> thisItemType = getType(key);
 341             if (otItemType == null ||
 342                     !thisItemType.isAssignableFrom(otItemType))
 343                 return false;
 344         }
 345         return true;
 346     }
 347 
 348 
 349     /* *** Methods overriden from class Object *** */
 350 
 351     /**
 352      * Compares the specified <code>obj</code> parameter with this <code>CompositeType</code> instance for equality.
 353      * <p>
 354      * Two <code>CompositeType</code> instances are equal if and only if all of the following statements are true:
 355      * <ul>
 356      * <li>their type names are equal</li>
 357      * <li>their items' names and types are equal</li>
 358      * </ul>
 359      *
 360      * @param  obj  the object to be compared for equality with this <code>CompositeType</code> instance;
 361      *              if <var>obj</var> is <code>null</code>, <code>equals</code> returns <code>false</code>.
 362      *
 363      * @return  <code>true</code> if the specified object is equal to this <code>CompositeType</code> instance.
 364      */
 365     public boolean equals(Object obj) {
 366 
 367         // if obj is null, return false
 368         //
 369         if (obj == null) {
 370             return false;
 371         }
 372 
 373         // if obj is not a CompositeType, return false
 374         //
 375         CompositeType other;
 376         try {
 377             other = (CompositeType) obj;
 378         } catch (ClassCastException e) {
 379             return false;
 380         }
 381 
 382         // Now, really test for equality between this CompositeType instance and the other
 383         //
 384 
 385         // their names should be equal
 386         if ( ! this.getTypeName().equals(other.getTypeName()) ) {
 387             return false;
 388         }
 389 
 390         // their items names and types should be equal
 391         if ( ! this.nameToType.equals(other.nameToType) ) {
 392             return false;
 393         }
 394 
 395         // All tests for equality were successfull
 396         //
 397         return true;
 398     }
 399 
 400     /**
 401      * Returns the hash code value for this <code>CompositeType</code> instance.
 402      * <p>
 403      * The hash code of a <code>CompositeType</code> instance is the sum of the hash codes
 404      * of all elements of information used in <code>equals</code> comparisons
 405      * (ie: name, items names, items types).
 406      * This ensures that <code> t1.equals(t2) </code> implies that <code> t1.hashCode()==t2.hashCode() </code>
 407      * for any two <code>CompositeType</code> instances <code>t1</code> and <code>t2</code>,
 408      * as required by the general contract of the method
 409      * {@link Object#hashCode() Object.hashCode()}.
 410      * <p>
 411      * As <code>CompositeType</code> instances are immutable, the hash code for this instance is calculated once,
 412      * on the first call to <code>hashCode</code>, and then the same value is returned for subsequent calls.
 413      *
 414      * @return  the hash code value for this <code>CompositeType</code> instance
 415      */
 416     public int hashCode() {
 417 
 418         // Calculate the hash code value if it has not yet been done (ie 1st call to hashCode())
 419         //
 420         if (myHashCode == null) {
 421             int value = 0;
 422             value += this.getTypeName().hashCode();
 423             for (String key : nameToDescription.keySet()) {
 424                 value += key.hashCode();
 425                 value += this.nameToType.get(key).hashCode();
 426             }
 427             myHashCode = Integer.valueOf(value);
 428         }
 429 
 430         // return always the same hash code for this instance (immutable)
 431         //
 432         return myHashCode.intValue();
 433     }
 434 
 435     /**
 436      * Returns a string representation of this <code>CompositeType</code> instance.
 437      * <p>
 438      * The string representation consists of
 439      * the name of this class (ie <code>javax.management.openmbean.CompositeType</code>), the type name for this instance,
 440      * and the list of the items names and types string representation of this instance.
 441      * <p>
 442      * As <code>CompositeType</code> instances are immutable, the string representation for this instance is calculated once,
 443      * on the first call to <code>toString</code>, and then the same value is returned for subsequent calls.
 444      *
 445      * @return  a string representation of this <code>CompositeType</code> instance
 446      */
 447     public String toString() {
 448 
 449         // Calculate the string representation if it has not yet been done (ie 1st call to toString())
 450         //
 451         if (myToString == null) {
 452             final StringBuilder result = new StringBuilder();
 453             result.append(this.getClass().getName());
 454             result.append("(name=");
 455             result.append(getTypeName());
 456             result.append(",items=(");
 457             int i=0;
 458             Iterator<String> k=nameToType.keySet().iterator();
 459             String key;
 460             while (k.hasNext()) {
 461                 key = k.next();
 462                 if (i > 0) result.append(",");
 463                 result.append("(itemName=");
 464                 result.append(key);
 465                 result.append(",itemType=");
 466                 result.append(nameToType.get(key).toString() +")");
 467                 i++;
 468             }
 469             result.append("))");
 470             myToString = result.toString();
 471         }
 472 
 473         // return always the same string representation for this instance (immutable)
 474         //
 475         return myToString;
 476     }
 477 
 478 }