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 }
355