1 /*
   2  * Copyright (c) 2000, 2013, 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.lang.reflect.Array;
  33 import java.lang.reflect.Constructor;
  34 import java.lang.reflect.Method;
  35 import java.lang.reflect.Modifier;
  36 import java.util.Arrays;
  37 import java.util.Collection;
  38 import java.util.Collections;
  39 import java.util.HashMap;
  40 import java.util.HashSet;
  41 import java.util.Map;
  42 import java.util.Set;
  43 import javax.management.Descriptor;
  44 import javax.management.DescriptorRead;
  45 import javax.management.ImmutableDescriptor;
  46 import javax.management.MBeanAttributeInfo;
  47 import com.sun.jmx.remote.util.EnvHelp;
  48 import sun.reflect.misc.ConstructorUtil;
  49 import sun.reflect.misc.MethodUtil;
  50 import sun.reflect.misc.ReflectUtil;
  51 
  52 /**
  53  * Describes an attribute of an open MBean.
  54  *
  55  *
  56  * @since 1.5
  57  */
  58 public class OpenMBeanAttributeInfoSupport
  59     extends MBeanAttributeInfo
  60     implements OpenMBeanAttributeInfo {
  61 
  62     /* Serial version */
  63     static final long serialVersionUID = -4867215622149721849L;
  64 
  65     /**
  66      * @serial The open mbean attribute's <i>open type</i>
  67      */
  68     private OpenType<?> openType;
  69 
  70     /**
  71      * @serial The open mbean attribute's default value
  72      */
  73     private final Object defaultValue;
  74 
  75     /**
  76      * @serial The open mbean attribute's legal values. This {@link
  77      * Set} is unmodifiable
  78      */
  79     private final Set<?> legalValues;  // to be constructed unmodifiable
  80 
  81     /**
  82      * @serial The open mbean attribute's min value
  83      */
  84     private final Comparable<?> minValue;
  85 
  86     /**
  87      * @serial The open mbean attribute's max value
  88      */
  89     private final Comparable<?> maxValue;
  90 
  91 
  92     // As this instance is immutable, these two values need only
  93     // be calculated once.
  94     private transient Integer myHashCode = null;
  95     private transient String  myToString = null;
  96 
  97 
  98     /**
  99      * Constructs an {@code OpenMBeanAttributeInfoSupport} instance,
 100      * which describes the attribute of an open MBean with the
 101      * specified {@code name}, {@code openType} and {@code
 102      * description}, and the specified read/write access properties.
 103      *
 104      * @param name  cannot be a null or empty string.
 105      *
 106      * @param description  cannot be a null or empty string.
 107      *
 108      * @param openType  cannot be null.
 109      *
 110      * @param isReadable {@code true} if the attribute has a getter
 111      * exposed for management.
 112      *
 113      * @param isWritable {@code true} if the attribute has a setter
 114      * exposed for management.
 115      *
 116      * @param isIs {@code true} if the attribute's getter is of the
 117      * form <tt>is<i>XXX</i></tt>.
 118      *
 119      * @throws IllegalArgumentException if {@code name} or {@code
 120      * description} are null or empty string, or {@code openType} is
 121      * null.
 122      */
 123     public OpenMBeanAttributeInfoSupport(String name,
 124                                          String description,
 125                                          OpenType<?> openType,
 126                                          boolean isReadable,
 127                                          boolean isWritable,
 128                                          boolean isIs) {
 129         this(name, description, openType, isReadable, isWritable, isIs,
 130              (Descriptor) null);
 131     }
 132 
 133     /**
 134      * <p>Constructs an {@code OpenMBeanAttributeInfoSupport} instance,
 135      * which describes the attribute of an open MBean with the
 136      * specified {@code name}, {@code openType}, {@code
 137      * description}, read/write access properties, and {@code Descriptor}.</p>
 138      *
 139      * <p>The {@code descriptor} can contain entries that will define
 140      * the values returned by certain methods of this class, as
 141      * explained in the <a href="package-summary.html#constraints">
 142      * package description</a>.
 143      *
 144      * @param name  cannot be a null or empty string.
 145      *
 146      * @param description  cannot be a null or empty string.
 147      *
 148      * @param openType  cannot be null.
 149      *
 150      * @param isReadable {@code true} if the attribute has a getter
 151      * exposed for management.
 152      *
 153      * @param isWritable {@code true} if the attribute has a setter
 154      * exposed for management.
 155      *
 156      * @param isIs {@code true} if the attribute's getter is of the
 157      * form <tt>is<i>XXX</i></tt>.
 158      *
 159      * @param descriptor The descriptor for the attribute.  This may be null
 160      * which is equivalent to an empty descriptor.
 161      *
 162      * @throws IllegalArgumentException if {@code name} or {@code
 163      * description} are null or empty string, or {@code openType} is
 164      * null, or the descriptor entries are invalid as described in the
 165      * <a href="package-summary.html#constraints">package description</a>.
 166      *
 167      * @since 1.6
 168      */
 169     public OpenMBeanAttributeInfoSupport(String name,
 170                                          String description,
 171                                          OpenType<?> openType,
 172                                          boolean isReadable,
 173                                          boolean isWritable,
 174                                          boolean isIs,
 175                                          Descriptor descriptor) {
 176         // Construct parent's state
 177         //
 178         super(name,
 179               (openType==null) ? null : openType.getClassName(),
 180               description,
 181               isReadable,
 182               isWritable,
 183               isIs,
 184               ImmutableDescriptor.union(descriptor, (openType==null)?null:
 185                 openType.getDescriptor()));
 186 
 187         // Initialize this instance's specific state
 188         //
 189         this.openType = openType;
 190 
 191         descriptor = getDescriptor();  // replace null by empty
 192         this.defaultValue = valueFrom(descriptor, "defaultValue", openType);
 193         this.legalValues = valuesFrom(descriptor, "legalValues", openType);
 194         this.minValue = comparableValueFrom(descriptor, "minValue", openType);
 195         this.maxValue = comparableValueFrom(descriptor, "maxValue", openType);
 196 
 197         try {
 198             check(this);
 199         } catch (OpenDataException e) {
 200             throw new IllegalArgumentException(e.getMessage(), e);
 201         }
 202     }
 203 
 204     /**
 205      * Constructs an {@code OpenMBeanAttributeInfoSupport} instance,
 206      * which describes the attribute of an open MBean with the
 207      * specified {@code name}, {@code openType}, {@code description}
 208      * and {@code defaultValue}, and the specified read/write access
 209      * properties.
 210      *
 211      * @param name  cannot be a null or empty string.
 212      *
 213      * @param description  cannot be a null or empty string.
 214      *
 215      * @param openType  cannot be null.
 216      *
 217      * @param isReadable {@code true} if the attribute has a getter
 218      * exposed for management.
 219      *
 220      * @param isWritable {@code true} if the attribute has a setter
 221      * exposed for management.
 222      *
 223      * @param isIs {@code true} if the attribute's getter is of the
 224      * form <tt>is<i>XXX</i></tt>.
 225      *
 226      * @param defaultValue must be a valid value for the {@code
 227      * openType} specified for this attribute; default value not
 228      * supported for {@code ArrayType} and {@code TabularType}; can
 229      * be null, in which case it means that no default value is set.
 230      *
 231      * @param <T> allows the compiler to check that the {@code defaultValue},
 232      * if non-null, has the correct Java type for the given {@code openType}.
 233      *
 234      * @throws IllegalArgumentException if {@code name} or {@code
 235      * description} are null or empty string, or {@code openType} is
 236      * null.
 237      *
 238      * @throws OpenDataException if {@code defaultValue} is not a
 239      * valid value for the specified {@code openType}, or {@code
 240      * defaultValue} is non null and {@code openType} is an {@code
 241      * ArrayType} or a {@code TabularType}.
 242      */
 243     public <T> OpenMBeanAttributeInfoSupport(String   name,
 244                                              String   description,
 245                                              OpenType<T> openType,
 246                                              boolean  isReadable,
 247                                              boolean  isWritable,
 248                                              boolean  isIs,
 249                                              T        defaultValue)
 250             throws OpenDataException {
 251         this(name, description, openType, isReadable, isWritable, isIs,
 252              defaultValue, (T[]) null);
 253     }
 254 
 255 
 256     /**
 257      * <p>Constructs an {@code OpenMBeanAttributeInfoSupport} instance,
 258      * which describes the attribute of an open MBean with the
 259      * specified {@code name}, {@code openType}, {@code description},
 260      * {@code defaultValue} and {@code legalValues}, and the specified
 261      * read/write access properties.</p>
 262      *
 263      * <p>The contents of {@code legalValues} are copied, so subsequent
 264      * modifications of the array referenced by {@code legalValues}
 265      * have no impact on this {@code OpenMBeanAttributeInfoSupport}
 266      * instance.</p>
 267      *
 268      * @param name  cannot be a null or empty string.
 269      *
 270      * @param description  cannot be a null or empty string.
 271      *
 272      * @param openType  cannot be null.
 273      *
 274      * @param isReadable {@code true} if the attribute has a getter
 275      * exposed for management.
 276      *
 277      * @param isWritable {@code true} if the attribute has a setter
 278      * exposed for management.
 279      *
 280      * @param isIs {@code true} if the attribute's getter is of the
 281      * form <tt>is<i>XXX</i></tt>.
 282      *
 283      * @param defaultValue must be a valid value
 284      * for the {@code
 285      * openType} specified for this attribute; default value not
 286      * supported for {@code ArrayType} and {@code TabularType}; can
 287      * be null, in which case it means that no default value is set.
 288      *
 289      * @param legalValues each contained value must be valid for the
 290      * {@code openType} specified for this attribute; legal values
 291      * not supported for {@code ArrayType} and {@code TabularType};
 292      * can be null or empty.
 293      *
 294      * @param <T> allows the compiler to check that the {@code
 295      * defaultValue} and {@code legalValues}, if non-null, have the
 296      * correct Java type for the given {@code openType}.
 297      *
 298      * @throws IllegalArgumentException if {@code name} or {@code
 299      * description} are null or empty string, or {@code openType} is
 300      * null.
 301      *
 302      * @throws OpenDataException if {@code defaultValue} is not a
 303      * valid value for the specified {@code openType}, or one value in
 304      * {@code legalValues} is not valid for the specified {@code
 305      * openType}, or {@code defaultValue} is non null and {@code
 306      * openType} is an {@code ArrayType} or a {@code TabularType}, or
 307      * {@code legalValues} is non null and non empty and {@code
 308      * openType} is an {@code ArrayType} or a {@code TabularType}, or
 309      * {@code legalValues} is non null and non empty and {@code
 310      * defaultValue} is not contained in {@code legalValues}.
 311      */
 312     public <T> OpenMBeanAttributeInfoSupport(String   name,
 313                                              String   description,
 314                                              OpenType<T> openType,
 315                                              boolean  isReadable,
 316                                              boolean  isWritable,
 317                                              boolean  isIs,
 318                                              T        defaultValue,
 319                                              T[]      legalValues)
 320             throws OpenDataException {
 321         this(name, description, openType, isReadable, isWritable, isIs,
 322              defaultValue, legalValues, null, null);
 323     }
 324 
 325 
 326     /**
 327      * Constructs an {@code OpenMBeanAttributeInfoSupport} instance,
 328      * which describes the attribute of an open MBean, with the
 329      * specified {@code name}, {@code openType}, {@code description},
 330      * {@code defaultValue}, {@code minValue} and {@code maxValue}.
 331      *
 332      * It is possible to specify minimal and maximal values only for
 333      * an open type whose values are {@code Comparable}.
 334      *
 335      * @param name  cannot be a null or empty string.
 336      *
 337      * @param description  cannot be a null or empty string.
 338      *
 339      * @param openType  cannot be null.
 340      *
 341      * @param isReadable {@code true} if the attribute has a getter
 342      * exposed for management.
 343      *
 344      * @param isWritable {@code true} if the attribute has a setter
 345      * exposed for management.
 346      *
 347      * @param isIs {@code true} if the attribute's getter is of the
 348      * form <tt>is<i>XXX</i></tt>.
 349      *
 350      * @param defaultValue must be a valid value for the {@code
 351      * openType} specified for this attribute; default value not
 352      * supported for {@code ArrayType} and {@code TabularType}; can be
 353      * null, in which case it means that no default value is set.
 354      *
 355      * @param minValue must be valid for the {@code openType}
 356      * specified for this attribute; can be null, in which case it
 357      * means that no minimal value is set.
 358      *
 359      * @param maxValue must be valid for the {@code openType}
 360      * specified for this attribute; can be null, in which case it
 361      * means that no maximal value is set.
 362      *
 363      * @param <T> allows the compiler to check that the {@code
 364      * defaultValue}, {@code minValue}, and {@code maxValue}, if
 365      * non-null, have the correct Java type for the given {@code
 366      * openType}.
 367      *
 368      * @throws IllegalArgumentException if {@code name} or {@code
 369      * description} are null or empty string, or {@code openType} is
 370      * null.
 371      *
 372      * @throws OpenDataException if {@code defaultValue}, {@code
 373      * minValue} or {@code maxValue} is not a valid value for the
 374      * specified {@code openType}, or {@code defaultValue} is non null
 375      * and {@code openType} is an {@code ArrayType} or a {@code
 376      * TabularType}, or both {@code minValue} and {@code maxValue} are
 377      * non-null and {@code minValue.compareTo(maxValue) > 0} is {@code
 378      * true}, or both {@code defaultValue} and {@code minValue} are
 379      * non-null and {@code minValue.compareTo(defaultValue) > 0} is
 380      * {@code true}, or both {@code defaultValue} and {@code maxValue}
 381      * are non-null and {@code defaultValue.compareTo(maxValue) > 0}
 382      * is {@code true}.
 383      */
 384     public <T> OpenMBeanAttributeInfoSupport(String     name,
 385                                              String     description,
 386                                              OpenType<T>   openType,
 387                                              boolean    isReadable,
 388                                              boolean    isWritable,
 389                                              boolean    isIs,
 390                                              T          defaultValue,
 391                                              Comparable<T> minValue,
 392                                              Comparable<T> maxValue)
 393             throws OpenDataException {
 394         this(name, description, openType, isReadable, isWritable, isIs,
 395              defaultValue, null, minValue, maxValue);
 396     }
 397 
 398     private <T> OpenMBeanAttributeInfoSupport(String name,
 399                                               String description,
 400                                               OpenType<T> openType,
 401                                               boolean isReadable,
 402                                               boolean isWritable,
 403                                               boolean isIs,
 404                                               T defaultValue,
 405                                               T[] legalValues,
 406                                               Comparable<T> minValue,
 407                                               Comparable<T> maxValue)
 408             throws OpenDataException {
 409         super(name,
 410               (openType==null) ? null : openType.getClassName(),
 411               description,
 412               isReadable,
 413               isWritable,
 414               isIs,
 415               makeDescriptor(openType,
 416                              defaultValue, legalValues, minValue, maxValue));
 417 
 418         this.openType = openType;
 419 
 420         Descriptor d = getDescriptor();
 421         this.defaultValue = defaultValue;
 422         this.minValue = minValue;
 423         this.maxValue = maxValue;
 424         // We already converted the array into an unmodifiable Set
 425         // in the descriptor.
 426         this.legalValues = (Set<?>) d.getFieldValue("legalValues");
 427 
 428         check(this);
 429     }
 430 
 431     /**
 432      * An object serialized in a version of the API before Descriptors were
 433      * added to this class will have an empty or null Descriptor.
 434      * For consistency with our
 435      * behavior in this version, we must replace the object with one
 436      * where the Descriptors reflect the same values of openType, defaultValue,
 437      * etc.
 438      **/
 439     private Object readResolve() {
 440         if (getDescriptor().getFieldNames().length == 0) {
 441             OpenType<Object> xopenType = cast(openType);
 442             Set<Object> xlegalValues = cast(legalValues);
 443             Comparable<Object> xminValue = cast(minValue);
 444             Comparable<Object> xmaxValue = cast(maxValue);
 445             return new OpenMBeanAttributeInfoSupport(
 446                     name, description, openType,
 447                     isReadable(), isWritable(), isIs(),
 448                     makeDescriptor(xopenType, defaultValue, xlegalValues,
 449                                    xminValue, xmaxValue));
 450         } else
 451             return this;
 452     }
 453 
 454     static void check(OpenMBeanParameterInfo info) throws OpenDataException {
 455         OpenType<?> openType = info.getOpenType();
 456         if (openType == null)
 457             throw new IllegalArgumentException("OpenType cannot be null");
 458 
 459         if (info.getName() == null ||
 460                 info.getName().trim().equals(""))
 461             throw new IllegalArgumentException("Name cannot be null or empty");
 462 
 463         if (info.getDescription() == null ||
 464                 info.getDescription().trim().equals(""))
 465             throw new IllegalArgumentException("Description cannot be null or empty");
 466 
 467         // Check and initialize defaultValue
 468         //
 469         if (info.hasDefaultValue()) {
 470             // Default value not supported for ArrayType and TabularType
 471             // Cast to Object because "OpenType<T> instanceof" is illegal
 472             if (openType.isArray() || (Object)openType instanceof TabularType) {
 473                 throw new OpenDataException("Default value not supported " +
 474                                             "for ArrayType and TabularType");
 475             }
 476             // Check defaultValue's class
 477             if (!openType.isValue(info.getDefaultValue())) {
 478                 final String msg =
 479                     "Argument defaultValue's class [\"" +
 480                     info.getDefaultValue().getClass().getName() +
 481                     "\"] does not match the one defined in openType[\"" +
 482                     openType.getClassName() +"\"]";
 483                 throw new OpenDataException(msg);
 484             }
 485         }
 486 
 487         // Check that we don't have both legalValues and min or max
 488         //
 489         if (info.hasLegalValues() &&
 490                 (info.hasMinValue() || info.hasMaxValue())) {
 491             throw new OpenDataException("cannot have both legalValue and " +
 492                                         "minValue or maxValue");
 493         }
 494 
 495         // Check minValue and maxValue
 496         if (info.hasMinValue() && !openType.isValue(info.getMinValue())) {
 497             final String msg =
 498                 "Type of minValue [" + info.getMinValue().getClass().getName() +
 499                 "] does not match OpenType [" + openType.getClassName() + "]";
 500             throw new OpenDataException(msg);
 501         }
 502         if (info.hasMaxValue() && !openType.isValue(info.getMaxValue())) {
 503             final String msg =
 504                 "Type of maxValue [" + info.getMaxValue().getClass().getName() +
 505                 "] does not match OpenType [" + openType.getClassName() + "]";
 506             throw new OpenDataException(msg);
 507         }
 508 
 509         // Check that defaultValue is a legal value
 510         //
 511         if (info.hasDefaultValue()) {
 512             Object defaultValue = info.getDefaultValue();
 513             if (info.hasLegalValues() &&
 514                     !info.getLegalValues().contains(defaultValue)) {
 515                 throw new OpenDataException("defaultValue is not contained " +
 516                                             "in legalValues");
 517             }
 518 
 519             // Check that minValue <= defaultValue <= maxValue
 520             //
 521             if (info.hasMinValue()) {
 522                 if (compare(info.getMinValue(), defaultValue) > 0) {
 523                     throw new OpenDataException("minValue cannot be greater " +
 524                                                 "than defaultValue");
 525                 }
 526             }
 527             if (info.hasMaxValue()) {
 528                 if (compare(info.getMaxValue(), defaultValue) < 0) {
 529                     throw new OpenDataException("maxValue cannot be less " +
 530                                                 "than defaultValue");
 531                 }
 532             }
 533         }
 534 
 535         // Check legalValues
 536         //
 537         if (info.hasLegalValues()) {
 538             // legalValues not supported for TabularType and arrays
 539             if ((Object)openType instanceof TabularType || openType.isArray()) {
 540                 throw new OpenDataException("Legal values not supported " +
 541                                             "for TabularType and arrays");
 542             }
 543             // Check legalValues are valid with openType
 544             for (Object v : info.getLegalValues()) {
 545                 if (!openType.isValue(v)) {
 546                     final String msg =
 547                         "Element of legalValues [" + v +
 548                         "] is not a valid value for the specified openType [" +
 549                         openType.toString() +"]";
 550                     throw new OpenDataException(msg);
 551                 }
 552             }
 553         }
 554 
 555 
 556         // Check that, if both specified, minValue <= maxValue
 557         //
 558         if (info.hasMinValue() && info.hasMaxValue()) {
 559             if (compare(info.getMinValue(), info.getMaxValue()) > 0) {
 560                 throw new OpenDataException("minValue cannot be greater " +
 561                                             "than maxValue");
 562             }
 563         }
 564 
 565     }
 566 
 567     @SuppressWarnings({"unchecked", "rawtypes"})
 568     static int compare(Object x, Object y) {
 569         return ((Comparable) x).compareTo(y);
 570     }
 571 
 572     static <T> Descriptor makeDescriptor(OpenType<T> openType,
 573                                          T defaultValue,
 574                                          T[] legalValues,
 575                                          Comparable<T> minValue,
 576                                          Comparable<T> maxValue) {
 577         Map<String, Object> map = new HashMap<String, Object>();
 578         if (defaultValue != null)
 579             map.put("defaultValue", defaultValue);
 580         if (legalValues != null) {
 581             Set<T> set = new HashSet<T>();
 582             for (T v : legalValues)
 583                 set.add(v);
 584             set = Collections.unmodifiableSet(set);
 585             map.put("legalValues", set);
 586         }
 587         if (minValue != null)
 588             map.put("minValue", minValue);
 589         if (maxValue != null)
 590             map.put("maxValue", maxValue);
 591         if (map.isEmpty()) {
 592             return openType.getDescriptor();
 593         } else {
 594             map.put("openType", openType);
 595             return new ImmutableDescriptor(map);
 596         }
 597     }
 598 
 599     static <T> Descriptor makeDescriptor(OpenType<T> openType,
 600                                          T defaultValue,
 601                                          Set<T> legalValues,
 602                                          Comparable<T> minValue,
 603                                          Comparable<T> maxValue) {
 604         T[] legals;
 605         if (legalValues == null)
 606             legals = null;
 607         else {
 608             legals = cast(new Object[legalValues.size()]);
 609             legalValues.toArray(legals);
 610         }
 611         return makeDescriptor(openType, defaultValue, legals, minValue, maxValue);
 612     }
 613 
 614 
 615     static <T> T valueFrom(Descriptor d, String name, OpenType<T> openType) {
 616         Object x = d.getFieldValue(name);
 617         if (x == null)
 618             return null;
 619         try {
 620             return convertFrom(x, openType);
 621         } catch (Exception e) {
 622             final String msg =
 623                 "Cannot convert descriptor field " + name + "  to " +
 624                 openType.getTypeName();
 625             throw EnvHelp.initCause(new IllegalArgumentException(msg), e);
 626         }
 627     }
 628 
 629     static <T> Set<T> valuesFrom(Descriptor d, String name,
 630                                  OpenType<T> openType) {
 631         Object x = d.getFieldValue(name);
 632         if (x == null)
 633             return null;
 634         Collection<?> coll;
 635         if (x instanceof Set<?>) {
 636             Set<?> set = (Set<?>) x;
 637             boolean asis = true;
 638             for (Object element : set) {
 639                 if (!openType.isValue(element)) {
 640                     asis = false;
 641                     break;
 642                 }
 643             }
 644             if (asis)
 645                 return cast(set);
 646             coll = set;
 647         } else if (x instanceof Object[]) {
 648             coll = Arrays.asList((Object[]) x);
 649         } else {
 650             final String msg =
 651                 "Descriptor value for " + name + " must be a Set or " +
 652                 "an array: " + x.getClass().getName();
 653             throw new IllegalArgumentException(msg);
 654         }
 655 
 656         Set<T> result = new HashSet<T>();
 657         for (Object element : coll)
 658             result.add(convertFrom(element, openType));
 659         return result;
 660     }
 661 
 662     static <T> Comparable<?> comparableValueFrom(Descriptor d, String name,
 663                                                  OpenType<T> openType) {
 664         T t = valueFrom(d, name, openType);
 665         if (t == null || t instanceof Comparable<?>)
 666             return (Comparable<?>) t;
 667         final String msg =
 668             "Descriptor field " + name + " with value " + t +
 669             " is not Comparable";
 670         throw new IllegalArgumentException(msg);
 671     }
 672 
 673     private static <T> T convertFrom(Object x, OpenType<T> openType) {
 674         if (openType.isValue(x)) {
 675             T t = OpenMBeanAttributeInfoSupport.<T>cast(x);
 676             return t;
 677         }
 678         return convertFromStrings(x, openType);
 679     }
 680 
 681     private static <T> T convertFromStrings(Object x, OpenType<T> openType) {
 682         if (openType instanceof ArrayType<?>)
 683             return convertFromStringArray(x, openType);
 684         else if (x instanceof String)
 685             return convertFromString((String) x, openType);
 686         final String msg =
 687             "Cannot convert value " + x + " of type " +
 688             x.getClass().getName() + " to type " + openType.getTypeName();
 689         throw new IllegalArgumentException(msg);
 690     }
 691 
 692     private static <T> T convertFromString(String s, OpenType<T> openType) {
 693         Class<T> c;
 694         try {
 695             ReflectUtil.checkPackageAccess(openType.safeGetClassName());
 696             c = cast(Class.forName(openType.safeGetClassName()));
 697         } catch (ClassNotFoundException e) {
 698             throw new NoClassDefFoundError(e.toString());  // can't happen
 699         }
 700 
 701         // Look for: public static T valueOf(String)
 702         Method valueOf;
 703         try {
 704             // It is safe to call this plain Class.getMethod because the class "c"
 705             // was checked before by ReflectUtil.checkPackageAccess(openType.safeGetClassName());
 706             valueOf = c.getMethod("valueOf", String.class);
 707             if (!Modifier.isStatic(valueOf.getModifiers()) ||
 708                     valueOf.getReturnType() != c)
 709                 valueOf = null;
 710         } catch (NoSuchMethodException e) {
 711             valueOf = null;
 712         }
 713         if (valueOf != null) {
 714             try {
 715                 return c.cast(MethodUtil.invoke(valueOf, null, new Object[] {s}));
 716             } catch (Exception e) {
 717                 final String msg =
 718                     "Could not convert \"" + s + "\" using method: " + valueOf;
 719                 throw new IllegalArgumentException(msg, e);
 720             }
 721         }
 722 
 723         // Look for: public T(String)
 724         Constructor<T> con;
 725         try {
 726             // It is safe to call this plain Class.getConstructor because the class "c"
 727             // was checked before by ReflectUtil.checkPackageAccess(openType.safeGetClassName());
 728             con = c.getConstructor(String.class);
 729         } catch (NoSuchMethodException e) {
 730             con = null;
 731         }
 732         if (con != null) {
 733             try {
 734                 return con.newInstance(s);
 735             } catch (Exception e) {
 736                 final String msg =
 737                     "Could not convert \"" + s + "\" using constructor: " + con;
 738                 throw new IllegalArgumentException(msg, e);
 739             }
 740         }
 741 
 742         throw new IllegalArgumentException("Don't know how to convert " +
 743                                            "string to " +
 744                                            openType.getTypeName());
 745     }
 746 
 747 
 748     /* A Descriptor contained an array value encoded as Strings.  The
 749        Strings must be organized in an array corresponding to the desired
 750        array.  If the desired array has n dimensions, so must the String
 751        array.  We will convert element by element from String to desired
 752        component type. */
 753     private static <T> T convertFromStringArray(Object x,
 754                                                 OpenType<T> openType) {
 755         ArrayType<?> arrayType = (ArrayType<?>) openType;
 756         OpenType<?> baseType = arrayType.getElementOpenType();
 757         int dim = arrayType.getDimension();
 758         String squareBrackets = "[";
 759         for (int i = 1; i < dim; i++)
 760             squareBrackets += "[";
 761         Class<?> stringArrayClass;
 762         Class<?> targetArrayClass;
 763         try {
 764             stringArrayClass =
 765                 Class.forName(squareBrackets + "Ljava.lang.String;");
 766             targetArrayClass =
 767                 Class.forName(squareBrackets + "L" + baseType.safeGetClassName() +
 768                               ";");
 769         } catch (ClassNotFoundException e) {
 770             throw new NoClassDefFoundError(e.toString());  // can't happen
 771         }
 772         if (!stringArrayClass.isInstance(x)) {
 773             final String msg =
 774                 "Value for " + dim + "-dimensional array of " +
 775                 baseType.getTypeName() + " must be same type or a String " +
 776                 "array with same dimensions";
 777             throw new IllegalArgumentException(msg);
 778         }
 779         OpenType<?> componentOpenType;
 780         if (dim == 1)
 781             componentOpenType = baseType;
 782         else {
 783             try {
 784                 componentOpenType = new ArrayType<T>(dim - 1, baseType);
 785             } catch (OpenDataException e) {
 786                 throw new IllegalArgumentException(e.getMessage(), e);
 787                 // can't happen
 788             }
 789         }
 790         int n = Array.getLength(x);
 791         Object[] targetArray = (Object[])
 792             Array.newInstance(targetArrayClass.getComponentType(), n);
 793         for (int i = 0; i < n; i++) {
 794             Object stringish = Array.get(x, i);  // String or String[] etc
 795             Object converted =
 796                 convertFromStrings(stringish, componentOpenType);
 797             Array.set(targetArray, i, converted);
 798         }
 799         return OpenMBeanAttributeInfoSupport.<T>cast(targetArray);
 800     }
 801 
 802     @SuppressWarnings("unchecked")
 803     static <T> T cast(Object x) {
 804         return (T) x;
 805     }
 806 
 807     /**
 808      * Returns the open type for the values of the attribute described
 809      * by this {@code OpenMBeanAttributeInfoSupport} instance.
 810      */
 811     public OpenType<?> getOpenType() {
 812         return openType;
 813     }
 814 
 815     /**
 816      * Returns the default value for the attribute described by this
 817      * {@code OpenMBeanAttributeInfoSupport} instance, if specified,
 818      * or {@code null} otherwise.
 819      */
 820     public Object getDefaultValue() {
 821 
 822         // Special case for ArrayType and TabularType
 823         // [JF] TODO: clone it so that it cannot be altered,
 824         // [JF] TODO: if we decide to support defaultValue as an array itself.
 825         // [JF] As of today (oct 2000) it is not supported so
 826         // defaultValue is null for arrays. Nothing to do.
 827 
 828         return defaultValue;
 829     }
 830 
 831     /**
 832      * Returns an unmodifiable Set of legal values for the attribute
 833      * described by this {@code OpenMBeanAttributeInfoSupport}
 834      * instance, if specified, or {@code null} otherwise.
 835      */
 836     public Set<?> getLegalValues() {
 837 
 838         // Special case for ArrayType and TabularType
 839         // [JF] TODO: clone values so that they cannot be altered,
 840         // [JF] TODO: if we decide to support LegalValues as an array itself.
 841         // [JF] As of today (oct 2000) it is not supported so
 842         // legalValues is null for arrays. Nothing to do.
 843 
 844         // Returns our legalValues Set (set was constructed unmodifiable)
 845         return (legalValues);
 846     }
 847 
 848     /**
 849      * Returns the minimal value for the attribute described by this
 850      * {@code OpenMBeanAttributeInfoSupport} instance, if specified,
 851      * or {@code null} otherwise.
 852      */
 853     public Comparable<?> getMinValue() {
 854 
 855         // Note: only comparable values have a minValue,
 856         // so that's not the case of arrays and tabulars (always null).
 857 
 858         return minValue;
 859     }
 860 
 861     /**
 862      * Returns the maximal value for the attribute described by this
 863      * {@code OpenMBeanAttributeInfoSupport} instance, if specified,
 864      * or {@code null} otherwise.
 865      */
 866     public Comparable<?> getMaxValue() {
 867 
 868         // Note: only comparable values have a maxValue,
 869         // so that's not the case of arrays and tabulars (always null).
 870 
 871         return maxValue;
 872     }
 873 
 874     /**
 875      * Returns {@code true} if this {@code
 876      * OpenMBeanAttributeInfoSupport} instance specifies a non-null
 877      * default value for the described attribute, {@code false}
 878      * otherwise.
 879      */
 880     public boolean hasDefaultValue() {
 881 
 882         return (defaultValue != null);
 883     }
 884 
 885     /**
 886      * Returns {@code true} if this {@code
 887      * OpenMBeanAttributeInfoSupport} instance specifies a non-null
 888      * set of legal values for the described attribute, {@code false}
 889      * otherwise.
 890      */
 891     public boolean hasLegalValues() {
 892 
 893         return (legalValues != null);
 894     }
 895 
 896     /**
 897      * Returns {@code true} if this {@code
 898      * OpenMBeanAttributeInfoSupport} instance specifies a non-null
 899      * minimal value for the described attribute, {@code false}
 900      * otherwise.
 901      */
 902     public boolean hasMinValue() {
 903 
 904         return (minValue != null);
 905     }
 906 
 907     /**
 908      * Returns {@code true} if this {@code
 909      * OpenMBeanAttributeInfoSupport} instance specifies a non-null
 910      * maximal value for the described attribute, {@code false}
 911      * otherwise.
 912      */
 913     public boolean hasMaxValue() {
 914 
 915         return (maxValue != null);
 916     }
 917 
 918 
 919     /**
 920      * Tests whether {@code obj} is a valid value for the attribute
 921      * described by this {@code OpenMBeanAttributeInfoSupport}
 922      * instance.
 923      *
 924      * @param obj the object to be tested.
 925      *
 926      * @return {@code true} if {@code obj} is a valid value for
 927      * the parameter described by this {@code
 928      * OpenMBeanAttributeInfoSupport} instance, {@code false}
 929      * otherwise.
 930      */
 931     public boolean isValue(Object obj) {
 932         return isValue(this, obj);
 933     }
 934 
 935     @SuppressWarnings({"unchecked", "rawtypes"})  // cast to Comparable
 936     static boolean isValue(OpenMBeanParameterInfo info, Object obj) {
 937         if (info.hasDefaultValue() && obj == null)
 938             return true;
 939         return
 940             info.getOpenType().isValue(obj) &&
 941             (!info.hasLegalValues() || info.getLegalValues().contains(obj)) &&
 942             (!info.hasMinValue() ||
 943             ((Comparable) info.getMinValue()).compareTo(obj) <= 0) &&
 944             (!info.hasMaxValue() ||
 945             ((Comparable) info.getMaxValue()).compareTo(obj) >= 0);
 946     }
 947 
 948     /* ***  Commodity methods from java.lang.Object  *** */
 949 
 950 
 951     /**
 952      * Compares the specified {@code obj} parameter with this {@code
 953      * OpenMBeanAttributeInfoSupport} instance for equality.
 954      * <p>
 955      * Returns {@code true} if and only if all of the following statements are true:
 956      * <ul>
 957      * <li>{@code obj} is non null,</li>
 958      * <li>{@code obj} also implements the {@code OpenMBeanAttributeInfo} interface,</li>
 959      * <li>their names are equal</li>
 960      * <li>their open types are equal</li>
 961      * <li>their access properties (isReadable, isWritable and isIs) are equal</li>
 962      * <li>their default, min, max and legal values are equal.</li>
 963      * </ul>
 964      * This ensures that this {@code equals} method works properly for
 965      * {@code obj} parameters which are different implementations of
 966      * the {@code OpenMBeanAttributeInfo} interface.
 967      *
 968      * <p>If {@code obj} also implements {@link DescriptorRead}, then its
 969      * {@link DescriptorRead#getDescriptor() getDescriptor()} method must
 970      * also return the same value as for this object.</p>
 971      *
 972      * @param obj the object to be compared for equality with this
 973      * {@code OpenMBeanAttributeInfoSupport} instance.
 974      *
 975      * @return {@code true} if the specified object is equal to this
 976      * {@code OpenMBeanAttributeInfoSupport} instance.
 977      */
 978     public boolean equals(Object obj) {
 979         if (!(obj instanceof OpenMBeanAttributeInfo))
 980             return false;
 981 
 982         OpenMBeanAttributeInfo other = (OpenMBeanAttributeInfo) obj;
 983 
 984         return
 985             this.isReadable() == other.isReadable() &&
 986             this.isWritable() == other.isWritable() &&
 987             this.isIs() == other.isIs() &&
 988             equal(this, other);
 989     }
 990 
 991     static boolean equal(OpenMBeanParameterInfo x1, OpenMBeanParameterInfo x2) {
 992         if (x1 instanceof DescriptorRead) {
 993             if (!(x2 instanceof DescriptorRead))
 994                 return false;
 995             Descriptor d1 = ((DescriptorRead) x1).getDescriptor();
 996             Descriptor d2 = ((DescriptorRead) x2).getDescriptor();
 997             if (!d1.equals(d2))
 998                 return false;
 999         } else if (x2 instanceof DescriptorRead)
1000             return false;
1001 
1002         return
1003             x1.getName().equals(x2.getName()) &&
1004             x1.getOpenType().equals(x2.getOpenType()) &&
1005             (x1.hasDefaultValue() ?
1006                 x1.getDefaultValue().equals(x2.getDefaultValue()) :
1007                 !x2.hasDefaultValue()) &&
1008             (x1.hasMinValue() ?
1009                 x1.getMinValue().equals(x2.getMinValue()) :
1010                 !x2.hasMinValue()) &&
1011             (x1.hasMaxValue() ?
1012                 x1.getMaxValue().equals(x2.getMaxValue()) :
1013                 !x2.hasMaxValue()) &&
1014             (x1.hasLegalValues() ?
1015                 x1.getLegalValues().equals(x2.getLegalValues()) :
1016                 !x2.hasLegalValues());
1017     }
1018 
1019     /**
1020      * <p>Returns the hash code value for this {@code
1021      * OpenMBeanAttributeInfoSupport} instance.</p>
1022      *
1023      * <p>The hash code of an {@code OpenMBeanAttributeInfoSupport}
1024      * instance is the sum of the hash codes of all elements of
1025      * information used in {@code equals} comparisons (ie: its name,
1026      * its <i>open type</i>, its default, min, max and legal
1027      * values, and its Descriptor).
1028      *
1029      * <p>This ensures that {@code t1.equals(t2)} implies that {@code
1030      * t1.hashCode()==t2.hashCode()} for any two {@code
1031      * OpenMBeanAttributeInfoSupport} instances {@code t1} and {@code
1032      * t2}, as required by the general contract of the method {@link
1033      * Object#hashCode() Object.hashCode()}.
1034      *
1035      * <p>However, note that another instance of a class implementing
1036      * the {@code OpenMBeanAttributeInfo} interface may be equal to
1037      * this {@code OpenMBeanAttributeInfoSupport} instance as defined
1038      * by {@link #equals(java.lang.Object)}, but may have a different
1039      * hash code if it is calculated differently.
1040      *
1041      * <p>As {@code OpenMBeanAttributeInfoSupport} instances are
1042      * immutable, the hash code for this instance is calculated once,
1043      * on the first call to {@code hashCode}, and then the same value
1044      * is returned for subsequent calls.
1045      *
1046      * @return the hash code value for this {@code
1047      * OpenMBeanAttributeInfoSupport} instance
1048      */
1049     public int hashCode() {
1050 
1051         // Calculate the hash code value if it has not yet been done
1052         // (ie 1st call to hashCode())
1053         //
1054         if (myHashCode == null)
1055             myHashCode = hashCode(this);
1056 
1057         // return always the same hash code for this instance (immutable)
1058         //
1059         return myHashCode.intValue();
1060     }
1061 
1062     static int hashCode(OpenMBeanParameterInfo info) {
1063         int value = 0;
1064         value += info.getName().hashCode();
1065         value += info.getOpenType().hashCode();
1066         if (info.hasDefaultValue())
1067             value += info.getDefaultValue().hashCode();
1068         if (info.hasMinValue())
1069             value += info.getMinValue().hashCode();
1070         if (info.hasMaxValue())
1071             value += info.getMaxValue().hashCode();
1072         if (info.hasLegalValues())
1073             value += info.getLegalValues().hashCode();
1074         if (info instanceof DescriptorRead)
1075             value += ((DescriptorRead) info).getDescriptor().hashCode();
1076         return value;
1077     }
1078 
1079     /**
1080      * Returns a string representation of this
1081      * {@code OpenMBeanAttributeInfoSupport} instance.
1082      * <p>
1083      * The string representation consists of the name of this class (i.e.
1084      * {@code javax.management.openmbean.OpenMBeanAttributeInfoSupport}),
1085      * the string representation of the name and open type of the
1086      * described parameter, the string representation of its
1087      * default, min, max and legal values and the string
1088      * representation of its descriptor.
1089      *
1090      * <p>As {@code OpenMBeanAttributeInfoSupport} instances are
1091      * immutable, the string representation for this instance is
1092      * calculated once, on the first call to {@code toString}, and
1093      * then the same value is returned for subsequent calls.
1094      *
1095      * @return a string representation of this
1096      * {@code OpenMBeanAttributeInfoSupport} instance.
1097      */
1098     public String toString() {
1099 
1100         // Calculate the string value if it has not yet been done
1101         // (ie 1st call to toString())
1102         //
1103         if (myToString == null)
1104             myToString = toString(this);
1105 
1106         // return always the same string representation for this
1107         // instance (immutable)
1108         //
1109         return myToString;
1110     }
1111 
1112     static String toString(OpenMBeanParameterInfo info) {
1113         Descriptor d = (info instanceof DescriptorRead) ?
1114             ((DescriptorRead) info).getDescriptor() : null;
1115         return
1116             info.getClass().getName() +
1117             "(name=" + info.getName() +
1118             ",openType=" + info.getOpenType() +
1119             ",default=" + info.getDefaultValue() +
1120             ",minValue=" + info.getMinValue() +
1121             ",maxValue=" + info.getMaxValue() +
1122             ",legalValues=" + info.getLegalValues() +
1123             ((d == null) ? "" : ",descriptor=" + d) +
1124             ")";
1125     }
1126 }