1 /*
   2  * Copyright (c) 1999, 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 package javax.management;
  27 
  28 import java.lang.reflect.Method;
  29 import java.security.AccessController;
  30 
  31 import com.sun.jmx.mbeanserver.GetPropertyAction;
  32 import com.sun.jmx.mbeanserver.Introspector;
  33 import java.util.Objects;
  34 
  35 
  36 /**
  37  * Describes an MBean attribute exposed for management.  Instances of
  38  * this class are immutable.  Subclasses may be mutable but this is
  39  * not recommended.
  40  *
  41  * @since 1.5
  42  */
  43 @SuppressWarnings("serial")  // serialVersionUID not constant
  44 public class MBeanAttributeInfo extends MBeanFeatureInfo implements Cloneable {
  45 
  46     /* Serial version */
  47     private static final long serialVersionUID;
  48     static {
  49         /* For complicated reasons, the serialVersionUID changed
  50            between JMX 1.0 and JMX 1.1, even though JMX 1.1 did not
  51            have compatibility code for this class.  So the
  52            serialization produced by this class with JMX 1.2 and
  53            jmx.serial.form=1.0 is not the same as that produced by
  54            this class with JMX 1.1 and jmx.serial.form=1.0.  However,
  55            the serialization without that property is the same, and
  56            that is the only form required by JMX 1.2.
  57         */
  58         long uid = 8644704819898565848L;
  59         try {
  60             GetPropertyAction act = new GetPropertyAction("jmx.serial.form");
  61             String form = AccessController.doPrivileged(act);
  62             if ("1.0".equals(form))
  63                 uid = 7043855487133450673L;
  64         } catch (Exception e) {
  65             // OK: exception means no compat with 1.0, too bad
  66         }
  67         serialVersionUID = uid;
  68     }
  69 
  70     static final MBeanAttributeInfo[] NO_ATTRIBUTES =
  71         new MBeanAttributeInfo[0];
  72 
  73     /**
  74      * @serial The actual attribute type.
  75      */
  76     private final String attributeType;
  77 
  78     /**
  79      * @serial The attribute write right.
  80      */
  81     private final boolean isWrite;
  82 
  83     /**
  84      * @serial The attribute read right.
  85      */
  86     private final boolean isRead;
  87 
  88     /**
  89      * @serial Indicates if this method is a "is"
  90      */
  91     private final boolean is;
  92 
  93 
  94     /**
  95      * Constructs an {@code MBeanAttributeInfo} object.
  96      *
  97      * @param name The name of the attribute.
  98      * @param type The type or class name of the attribute.
  99      * @param description A human readable description of the attribute.
 100      * @param isReadable True if the attribute has a getter method, false otherwise.
 101      * @param isWritable True if the attribute has a setter method, false otherwise.
 102      * @param isIs True if this attribute has an "is" getter, false otherwise.
 103      *
 104      * @throws IllegalArgumentException if {@code isIs} is true but
 105      * {@code isReadable} is not, or if {@code isIs} is true and
 106      * {@code type} is not {@code boolean} or {@code java.lang.Boolean}.
 107      * (New code should always use {@code boolean} rather than
 108      * {@code java.lang.Boolean}.)
 109      */
 110     public MBeanAttributeInfo(String name,
 111                               String type,
 112                               String description,
 113                               boolean isReadable,
 114                               boolean isWritable,
 115                               boolean isIs) {
 116         this(name, type, description, isReadable, isWritable, isIs,
 117              (Descriptor) null);
 118     }
 119 
 120     /**
 121      * Constructs an {@code MBeanAttributeInfo} object.
 122      *
 123      * @param name The name of the attribute.
 124      * @param type The type or class name of the attribute.
 125      * @param description A human readable description of the attribute.
 126      * @param isReadable True if the attribute has a getter method, false otherwise.
 127      * @param isWritable True if the attribute has a setter method, false otherwise.
 128      * @param isIs True if this attribute has an "is" getter, false otherwise.
 129      * @param descriptor The descriptor for the attribute.  This may be null
 130      * which is equivalent to an empty descriptor.
 131      *
 132      * @throws IllegalArgumentException if {@code isIs} is true but
 133      * {@code isReadable} is not, or if {@code isIs} is true and
 134      * {@code type} is not {@code boolean} or {@code java.lang.Boolean}.
 135      * (New code should always use {@code boolean} rather than
 136      * {@code java.lang.Boolean}.)
 137      *
 138      * @since 1.6
 139      */
 140     public MBeanAttributeInfo(String name,
 141                               String type,
 142                               String description,
 143                               boolean isReadable,
 144                               boolean isWritable,
 145                               boolean isIs,
 146                               Descriptor descriptor) {
 147         super(name, description, descriptor);
 148 
 149         this.attributeType = type;
 150         this.isRead = isReadable;
 151         this.isWrite = isWritable;
 152         if (isIs && !isReadable) {
 153             throw new IllegalArgumentException("Cannot have an \"is\" getter " +
 154                                                "for a non-readable attribute");
 155         }
 156         if (isIs && !type.equals("java.lang.Boolean") &&
 157                 !type.equals("boolean")) {
 158             throw new IllegalArgumentException("Cannot have an \"is\" getter " +
 159                                                "for a non-boolean attribute");
 160         }
 161         this.is = isIs;
 162     }
 163 
 164     /**
 165      * <p>This constructor takes the name of a simple attribute, and Method
 166      * objects for reading and writing the attribute.  The {@link Descriptor}
 167      * of the constructed object will include fields contributed by any
 168      * annotations on the {@code Method} objects that contain the
 169      * {@link DescriptorKey} meta-annotation.
 170      *
 171      * @param name The programmatic name of the attribute.
 172      * @param description A human readable description of the attribute.
 173      * @param getter The method used for reading the attribute value.
 174      *          May be null if the property is write-only.
 175      * @param setter The method used for writing the attribute value.
 176      *          May be null if the attribute is read-only.
 177      * @exception IntrospectionException There is a consistency
 178      * problem in the definition of this attribute.
 179      */
 180     public MBeanAttributeInfo(String name,
 181                               String description,
 182                               Method getter,
 183                               Method setter) throws IntrospectionException {
 184         this(name,
 185              attributeType(getter, setter),
 186              description,
 187              (getter != null),
 188              (setter != null),
 189              isIs(getter),
 190              ImmutableDescriptor.union(Introspector.descriptorForElement(getter),
 191                                    Introspector.descriptorForElement(setter)));
 192     }
 193 
 194     /**
 195      * <p>Returns a shallow clone of this instance.
 196      * The clone is obtained by simply calling {@code super.clone()},
 197      * thus calling the default native shallow cloning mechanism
 198      * implemented by {@code Object.clone()}.
 199      * No deeper cloning of any internal field is made.</p>
 200      *
 201      * <p>Since this class is immutable, cloning is chiefly of
 202      * interest to subclasses.</p>
 203      */
 204      public Object clone () {
 205          try {
 206              return super.clone() ;
 207          } catch (CloneNotSupportedException e) {
 208              // should not happen as this class is cloneable
 209              return null;
 210          }
 211      }
 212 
 213     /**
 214      * Returns the class name of the attribute.
 215      *
 216      * @return the class name.
 217      */
 218     public String getType() {
 219         return attributeType;
 220     }
 221 
 222     /**
 223      * Whether the value of the attribute can be read.
 224      *
 225      * @return True if the attribute can be read, false otherwise.
 226      */
 227     public boolean isReadable() {
 228         return isRead;
 229     }
 230 
 231     /**
 232      * Whether new values can be written to the attribute.
 233      *
 234      * @return True if the attribute can be written to, false otherwise.
 235      */
 236     public boolean isWritable() {
 237         return isWrite;
 238     }
 239 
 240     /**
 241      * Indicates if this attribute has an "is" getter.
 242      *
 243      * @return true if this attribute has an "is" getter.
 244      */
 245     public boolean isIs() {
 246         return is;
 247     }
 248 
 249     public String toString() {
 250         String access;
 251         if (isReadable()) {
 252             if (isWritable())
 253                 access = "read/write";
 254             else
 255                 access = "read-only";
 256         } else if (isWritable())
 257             access = "write-only";
 258         else
 259             access = "no-access";
 260 
 261         return
 262             getClass().getName() + "[" +
 263             "description=" + getDescription() + ", " +
 264             "name=" + getName() + ", " +
 265             "type=" + getType() + ", " +
 266             access + ", " +
 267             (isIs() ? "isIs, " : "") +
 268             "descriptor=" + getDescriptor() +
 269             "]";
 270     }
 271 
 272     /**
 273      * Compare this MBeanAttributeInfo to another.
 274      *
 275      * @param o the object to compare to.
 276      *
 277      * @return true if and only if {@code o} is an MBeanAttributeInfo such
 278      * that its {@link #getName()}, {@link #getType()}, {@link
 279      * #getDescription()}, {@link #isReadable()}, {@link
 280      * #isWritable()}, and {@link #isIs()} values are equal (not
 281      * necessarily identical) to those of this MBeanAttributeInfo.
 282      */
 283     public boolean equals(Object o) {
 284         if (o == this)
 285             return true;
 286         if (!(o instanceof MBeanAttributeInfo))
 287             return false;
 288         MBeanAttributeInfo p = (MBeanAttributeInfo) o;
 289         return (Objects.equals(p.getName(), getName()) &&
 290                 Objects.equals(p.getType(), getType()) &&
 291                 Objects.equals(p.getDescription(), getDescription()) &&
 292                 Objects.equals(p.getDescriptor(), getDescriptor()) &&
 293                 p.isReadable() == isReadable() &&
 294                 p.isWritable() == isWritable() &&
 295                 p.isIs() == isIs());
 296     }
 297 
 298     /* We do not include everything in the hashcode.  We assume that
 299        if two operations are different they'll probably have different
 300        names or types.  The penalty we pay when this assumption is
 301        wrong should be less than the penalty we would pay if it were
 302        right and we needlessly hashed in the description and parameter
 303        array.  */
 304     public int hashCode() {
 305         return Objects.hash(getName(), getType());
 306     }
 307 
 308     private static boolean isIs(Method getter) {
 309         return (getter != null &&
 310                 getter.getName().startsWith("is") &&
 311                 (getter.getReturnType().equals(Boolean.TYPE) ||
 312                  getter.getReturnType().equals(Boolean.class)));
 313     }
 314 
 315     /**
 316      * Finds the type of the attribute.
 317      */
 318     private static String attributeType(Method getter, Method setter)
 319             throws IntrospectionException {
 320         Class<?> type = null;
 321 
 322         if (getter != null) {
 323             if (getter.getParameterTypes().length != 0) {
 324                 throw new IntrospectionException("bad getter arg count");
 325             }
 326             type = getter.getReturnType();
 327             if (type == Void.TYPE) {
 328                 throw new IntrospectionException("getter " + getter.getName() +
 329                                                  " returns void");
 330             }
 331         }
 332 
 333         if (setter != null) {
 334             Class<?> params[] = setter.getParameterTypes();
 335             if (params.length != 1) {
 336                 throw new IntrospectionException("bad setter arg count");
 337             }
 338             if (type == null)
 339                 type = params[0];
 340             else if (type != params[0]) {
 341                 throw new IntrospectionException("type mismatch between " +
 342                                                  "getter and setter");
 343             }
 344         }
 345 
 346         if (type == null) {
 347             throw new IntrospectionException("getter and setter cannot " +
 348                                              "both be null");
 349         }
 350 
 351         return type.getName();
 352     }
 353 
 354 }