1 /*
2  * Copyright (c) 1999, 2019, 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 com.sun.jmx.mbeanserver;
27 
28 import java.lang.annotation.Annotation;
29 import java.lang.ref.SoftReference;
30 import java.lang.reflect.AnnotatedElement;
31 import java.lang.reflect.Constructor;
32 import java.lang.reflect.Method;
33 import java.lang.reflect.Modifier;
34 import java.lang.reflect.Proxy;
35 import java.lang.reflect.UndeclaredThrowableException;
36 import java.util.Arrays;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.LinkedList;
41 import java.util.Locale;
42 import java.util.Map;
43 import java.util.WeakHashMap;
44 
45 import javax.management.Descriptor;
46 import javax.management.DescriptorKey;
47 import javax.management.DynamicMBean;
48 import javax.management.ImmutableDescriptor;
49 import javax.management.MBeanInfo;
50 import javax.management.NotCompliantMBeanException;
51 
52 import com.sun.jmx.remote.util.EnvHelp;
53 import java.lang.reflect.Array;
54 import java.lang.reflect.InvocationTargetException;
55 import java.security.AccessController;
56 import javax.management.AttributeNotFoundException;
57 import javax.management.openmbean.CompositeData;
58 
59 import sun.reflect.misc.MethodUtil;
60 import sun.reflect.misc.ReflectUtil;
61 
62 /**
63  * This class contains the methods for performing all the tests needed to verify
64  * that a class represents a JMX compliant MBean.
65  *
66  * @since 1.5
67  */
68 public class Introspector {
69     final public static boolean ALLOW_NONPUBLIC_MBEAN;
70     static {
71         String val = AccessController.doPrivileged(new GetPropertyAction("jdk.jmx.mbeans.allowNonPublic"));
72         ALLOW_NONPUBLIC_MBEAN = Boolean.parseBoolean(val);
73     }
74 
75      /*
76      * ------------------------------------------
77      *  PRIVATE CONSTRUCTORS
78      * ------------------------------------------
79      */
80 
81     // private constructor defined to "hide" the default public constructor
Introspector()82     private Introspector() {
83 
84         // ------------------------------
85         // ------------------------------
86 
87     }
88 
89     /*
90      * ------------------------------------------
91      *  PUBLIC METHODS
92      * ------------------------------------------
93      */
94 
95     /**
96      * Tell whether a MBean of the given class is a Dynamic MBean.
97      * This method does nothing more than returning
98      * <pre>
99      * javax.management.DynamicMBean.class.isAssignableFrom(c)
100      * </pre>
101      * This method does not check for any JMX MBean compliance:
102      * <ul><li>If <code>true</code> is returned, then instances of
103      *     <code>c</code> are DynamicMBean.</li>
104      *     <li>If <code>false</code> is returned, then no further
105      *     assumption can be made on instances of <code>c</code>.
106      *     In particular, instances of <code>c</code> may, or may not
107      *     be JMX standard MBeans.</li>
108      * </ul>
109      * @param c The class of the MBean under examination.
110      * @return <code>true</code> if instances of <code>c</code> are
111      *         Dynamic MBeans, <code>false</code> otherwise.
112      *
113      **/
isDynamic(final Class<?> c)114     public static final boolean isDynamic(final Class<?> c) {
115         // Check if the MBean implements the DynamicMBean interface
116         return javax.management.DynamicMBean.class.isAssignableFrom(c);
117     }
118 
119     /**
120      * Basic method for testing that a MBean of a given class can be
121      * instantiated by the MBean server.<p>
122      * This method checks that:
123      * <ul><li>The given class is a concrete class.</li>
124      *     <li>The given class exposes at least one public constructor.</li>
125      * </ul>
126      * If these conditions are not met, throws a NotCompliantMBeanException.
127      * @param c The class of the MBean we want to create.
128      * @exception NotCompliantMBeanException if the MBean class makes it
129      *            impossible to instantiate the MBean from within the
130      *            MBeanServer.
131      *
132      **/
testCreation(Class<?> c)133     public static void testCreation(Class<?> c)
134         throws NotCompliantMBeanException {
135         // Check if the class is a concrete class
136         final int mods = c.getModifiers();
137         if (Modifier.isAbstract(mods) || Modifier.isInterface(mods)) {
138             throw new NotCompliantMBeanException("MBean class must be concrete");
139         }
140 
141         // Check if the MBean has a public constructor
142         final Constructor<?>[] consList = c.getConstructors();
143         if (consList.length == 0) {
144             throw new NotCompliantMBeanException("MBean class must have public constructor");
145         }
146     }
147 
checkCompliance(Class<?> mbeanClass)148     public static void checkCompliance(Class<?> mbeanClass)
149     throws NotCompliantMBeanException {
150         // Is DynamicMBean?
151         //
152         if (DynamicMBean.class.isAssignableFrom(mbeanClass))
153             return;
154         // Is Standard MBean?
155         //
156         final Exception mbeanException;
157         try {
158             getStandardMBeanInterface(mbeanClass);
159             return;
160         } catch (NotCompliantMBeanException e) {
161             mbeanException = e;
162         }
163         // Is MXBean?
164         //
165         final Exception mxbeanException;
166         try {
167             getMXBeanInterface(mbeanClass);
168             return;
169         } catch (NotCompliantMBeanException e) {
170             mxbeanException = e;
171         }
172         final String msg =
173             "MBean class " + mbeanClass.getName() + " does not implement " +
174             "DynamicMBean, and neither follows the Standard MBean conventions (" +
175             mbeanException.toString() + ") nor the MXBean conventions (" +
176             mxbeanException.toString() + ")";
177         throw new NotCompliantMBeanException(msg);
178     }
179 
makeDynamicMBean(T mbean)180     public static <T> DynamicMBean makeDynamicMBean(T mbean)
181         throws NotCompliantMBeanException {
182         if (mbean instanceof DynamicMBean)
183             return (DynamicMBean) mbean;
184         final Class<?> mbeanClass = mbean.getClass();
185         Class<? super T> c = null;
186         try {
187             c = Util.cast(getStandardMBeanInterface(mbeanClass));
188         } catch (NotCompliantMBeanException e) {
189             // Ignore exception - we need to check whether
190             // mbean is an MXBean first.
191         }
192         if (c != null)
193             return new StandardMBeanSupport(mbean, c);
194 
195         try {
196             c = Util.cast(getMXBeanInterface(mbeanClass));
197         } catch (NotCompliantMBeanException e) {
198             // Ignore exception - we cannot decide whether mbean was supposed
199             // to be an MBean or an MXBean. We will call checkCompliance()
200             // to generate the appropriate exception.
201         }
202         if (c != null)
203             return new MXBeanSupport(mbean, c);
204         checkCompliance(mbeanClass);
205         throw new NotCompliantMBeanException("Not compliant"); // not reached
206     }
207 
208     /**
209      * Basic method for testing if a given class is a JMX compliant MBean.
210      *
211      * @param baseClass The class to be tested
212      *
213      * @return <code>null</code> if the MBean is a DynamicMBean,
214      *         the computed {@link javax.management.MBeanInfo} otherwise.
215      * @exception NotCompliantMBeanException The specified class is not a
216      *            JMX compliant MBean
217      */
testCompliance(Class<?> baseClass)218     public static MBeanInfo testCompliance(Class<?> baseClass)
219         throws NotCompliantMBeanException {
220 
221         // ------------------------------
222         // ------------------------------
223 
224         // Check if the MBean implements the MBean or the Dynamic
225         // MBean interface
226         if (isDynamic(baseClass))
227             return null;
228 
229         return testCompliance(baseClass, null);
230     }
231 
232     /**
233      * Tests the given interface class for being a compliant MXBean interface.
234      * A compliant MXBean interface is any publicly accessible interface
235      * following the {@link MXBean} conventions.
236      * @param interfaceClass An interface class to test for the MXBean compliance
237      * @throws NotCompliantMBeanException Thrown when the tested interface
238      * is not public or contradicts the {@link MXBean} conventions.
239      */
testComplianceMXBeanInterface(Class<?> interfaceClass)240     public static void testComplianceMXBeanInterface(Class<?> interfaceClass)
241             throws NotCompliantMBeanException {
242         MXBeanIntrospector.getInstance().getAnalyzer(interfaceClass);
243     }
244 
245     /**
246      * Tests the given interface class for being a compliant MBean interface.
247      * A compliant MBean interface is any publicly accessible interface
248      * following the {@code MBean} conventions.
249      * @param interfaceClass An interface class to test for the MBean compliance
250      * @throws NotCompliantMBeanException Thrown when the tested interface
251      * is not public or contradicts the {@code MBean} conventions.
252      */
testComplianceMBeanInterface(Class<?> interfaceClass)253     public static void testComplianceMBeanInterface(Class<?> interfaceClass)
254             throws NotCompliantMBeanException{
255         StandardMBeanIntrospector.getInstance().getAnalyzer(interfaceClass);
256     }
257 
258     /**
259      * Basic method for testing if a given class is a JMX compliant
260      * Standard MBean.  This method is only called by the legacy code
261      * in com.sun.management.jmx.
262      *
263      * @param baseClass The class to be tested.
264      *
265      * @param mbeanInterface the MBean interface that the class implements,
266      * or null if the interface must be determined by introspection.
267      *
268      * @return the computed {@link javax.management.MBeanInfo}.
269      * @exception NotCompliantMBeanException The specified class is not a
270      *            JMX compliant Standard MBean
271      */
272     public static synchronized MBeanInfo
testCompliance(final Class<?> baseClass, Class<?> mbeanInterface)273             testCompliance(final Class<?> baseClass,
274                            Class<?> mbeanInterface)
275             throws NotCompliantMBeanException {
276         if (mbeanInterface == null)
277             mbeanInterface = getStandardMBeanInterface(baseClass);
278         ReflectUtil.checkPackageAccess(mbeanInterface);
279         MBeanIntrospector<?> introspector = StandardMBeanIntrospector.getInstance();
280         return getClassMBeanInfo(introspector, baseClass, mbeanInterface);
281     }
282 
283     private static <M> MBeanInfo
getClassMBeanInfo(MBeanIntrospector<M> introspector, Class<?> baseClass, Class<?> mbeanInterface)284             getClassMBeanInfo(MBeanIntrospector<M> introspector,
285                               Class<?> baseClass, Class<?> mbeanInterface)
286     throws NotCompliantMBeanException {
287         PerInterface<M> perInterface = introspector.getPerInterface(mbeanInterface);
288         return introspector.getClassMBeanInfo(baseClass, perInterface);
289     }
290 
291     /**
292      * Get the MBean interface implemented by a JMX Standard
293      * MBean class. This method is only called by the legacy
294      * code in "com.sun.management.jmx".
295      *
296      * @param baseClass The class to be tested.
297      *
298      * @return The MBean interface implemented by the MBean.
299      *         Return <code>null</code> if the MBean is a DynamicMBean,
300      *         or if no MBean interface is found.
301      */
getMBeanInterface(Class<?> baseClass)302     public static Class<?> getMBeanInterface(Class<?> baseClass) {
303         // Check if the given class implements the MBean interface
304         // or the Dynamic MBean interface
305         if (isDynamic(baseClass)) return null;
306         try {
307             return getStandardMBeanInterface(baseClass);
308         } catch (NotCompliantMBeanException e) {
309             return null;
310         }
311     }
312 
313     /**
314      * Get the MBean interface implemented by a JMX Standard MBean class.
315      *
316      * @param baseClass The class to be tested.
317      *
318      * @return The MBean interface implemented by the Standard MBean.
319      *
320      * @throws NotCompliantMBeanException The specified class is
321      * not a JMX compliant Standard MBean.
322      */
getStandardMBeanInterface(Class<T> baseClass)323     public static <T> Class<? super T> getStandardMBeanInterface(Class<T> baseClass)
324         throws NotCompliantMBeanException {
325             Class<? super T> current = baseClass;
326             Class<? super T> mbeanInterface = null;
327             while (current != null) {
328                 mbeanInterface =
329                     findMBeanInterface(current, current.getName());
330                 if (mbeanInterface != null) break;
331                 current = current.getSuperclass();
332             }
333                 if (mbeanInterface != null) {
334                     return mbeanInterface;
335             } else {
336             final String msg =
337                 "Class " + baseClass.getName() +
338                 " is not a JMX compliant Standard MBean";
339             throw new NotCompliantMBeanException(msg);
340         }
341     }
342 
343     /**
344      * Get the MXBean interface implemented by a JMX MXBean class.
345      *
346      * @param baseClass The class to be tested.
347      *
348      * @return The MXBean interface implemented by the MXBean.
349      *
350      * @throws NotCompliantMBeanException The specified class is
351      * not a JMX compliant MXBean.
352      */
getMXBeanInterface(Class<T> baseClass)353     public static <T> Class<? super T> getMXBeanInterface(Class<T> baseClass)
354         throws NotCompliantMBeanException {
355         try {
356             return MXBeanSupport.findMXBeanInterface(baseClass);
357         } catch (Exception e) {
358             throw throwException(baseClass,e);
359         }
360     }
361 
362     /*
363      * ------------------------------------------
364      *  PRIVATE METHODS
365      * ------------------------------------------
366      */
367 
368 
369     /**
370      * Try to find the MBean interface corresponding to the class aName
371      * - i.e. <i>aName</i>MBean, from within aClass and its superclasses.
372      **/
findMBeanInterface( Class<T> aClass, String aName)373     private static <T> Class<? super T> findMBeanInterface(
374             Class<T> aClass, String aName) {
375         Class<? super T> current = aClass;
376         while (current != null) {
377             final Class<?>[] interfaces = current.getInterfaces();
378             final int len = interfaces.length;
379             for (int i=0;i<len;i++)  {
380                 Class<? super T> inter = Util.cast(interfaces[i]);
381                 inter = implementsMBean(inter, aName);
382                 if (inter != null) return inter;
383             }
384             current = current.getSuperclass();
385         }
386         return null;
387     }
388 
descriptorForElement(final AnnotatedElement elmt)389     public static Descriptor descriptorForElement(final AnnotatedElement elmt) {
390         if (elmt == null)
391             return ImmutableDescriptor.EMPTY_DESCRIPTOR;
392         final Annotation[] annots = elmt.getAnnotations();
393         return descriptorForAnnotations(annots);
394     }
395 
descriptorForAnnotations(Annotation[] annots)396     public static Descriptor descriptorForAnnotations(Annotation[] annots) {
397         if (annots.length == 0)
398             return ImmutableDescriptor.EMPTY_DESCRIPTOR;
399         Map<String, Object> descriptorMap = new HashMap<String, Object>();
400         for (Annotation a : annots) {
401             Class<? extends Annotation> c = a.annotationType();
402             Method[] elements = c.getMethods();
403             boolean packageAccess = false;
404             for (Method element : elements) {
405                 DescriptorKey key = element.getAnnotation(DescriptorKey.class);
406                 if (key != null) {
407                     String name = key.value();
408                     Object value;
409                     try {
410                         // Avoid checking access more than once per annotation
411                         if (!packageAccess) {
412                             ReflectUtil.checkPackageAccess(c);
413                             packageAccess = true;
414                         }
415                         value = MethodUtil.invoke(element, a, null);
416                     } catch (RuntimeException e) {
417                         // we don't expect this - except for possibly
418                         // security exceptions?
419                         // RuntimeExceptions shouldn't be "UndeclaredThrowable".
420                         // anyway...
421                         //
422                         throw e;
423                     } catch (Exception e) {
424                         // we don't expect this
425                         throw new UndeclaredThrowableException(e);
426                     }
427                     value = annotationToField(value);
428                     Object oldValue = descriptorMap.put(name, value);
429                     if (oldValue != null && !equals(oldValue, value)) {
430                         final String msg =
431                             "Inconsistent values for descriptor field " + name +
432                             " from annotations: " + value + " :: " + oldValue;
433                         throw new IllegalArgumentException(msg);
434                     }
435                 }
436             }
437         }
438 
439         if (descriptorMap.isEmpty())
440             return ImmutableDescriptor.EMPTY_DESCRIPTOR;
441         else
442             return new ImmutableDescriptor(descriptorMap);
443     }
444 
445     /**
446      * Throws a NotCompliantMBeanException or a SecurityException.
447      * @param notCompliant the class which was under examination
448      * @param cause the raeson why NotCompliantMBeanException should
449      *        be thrown.
450      * @return nothing - this method always throw an exception.
451      *         The return type makes it possible to write
452      *         <pre> throw throwException(clazz,cause); </pre>
453      * @throws SecurityException   if cause is a SecurityException
454      * @throws NotCompliantMBeanException otherwise.
455      **/
throwException(Class<?> notCompliant, Throwable cause)456     static NotCompliantMBeanException throwException(Class<?> notCompliant,
457             Throwable cause)
458             throws NotCompliantMBeanException, SecurityException {
459         if (cause instanceof SecurityException)
460             throw (SecurityException) cause;
461         if (cause instanceof NotCompliantMBeanException)
462             throw (NotCompliantMBeanException)cause;
463         final String classname =
464                 (notCompliant==null)?"null class":notCompliant.getName();
465         final String reason =
466                 (cause==null)?"Not compliant":cause.getMessage();
467         final NotCompliantMBeanException res =
468                 new NotCompliantMBeanException(classname+": "+reason);
469         res.initCause(cause);
470         throw res;
471     }
472 
473     // Convert a value from an annotation element to a descriptor field value
474     // E.g. with @interface Foo {class value()} an annotation @Foo(String.class)
475     // will produce a Descriptor field value "java.lang.String"
annotationToField(Object x)476     private static Object annotationToField(Object x) {
477         // An annotation element cannot have a null value but never mind
478         if (x == null)
479             return null;
480         if (x instanceof Number || x instanceof String ||
481                 x instanceof Character || x instanceof Boolean ||
482                 x instanceof String[])
483             return x;
484         // Remaining possibilities: array of primitive (e.g. int[]),
485         // enum, class, array of enum or class.
486         Class<?> c = x.getClass();
487         if (c.isArray()) {
488             if (c.getComponentType().isPrimitive())
489                 return x;
490             Object[] xx = (Object[]) x;
491             String[] ss = new String[xx.length];
492             for (int i = 0; i < xx.length; i++)
493                 ss[i] = (String) annotationToField(xx[i]);
494             return ss;
495         }
496         if (x instanceof Class<?>)
497             return ((Class<?>) x).getName();
498         if (x instanceof Enum<?>)
499             return ((Enum<?>) x).name();
500         // The only other possibility is that the value is another
501         // annotation, or that the language has evolved since this code
502         // was written.  We don't allow for either of those currently.
503         // If it is indeed another annotation, then x will be a proxy
504         // with an unhelpful name like $Proxy2.  So we extract the
505         // proxy's interface to use that in the exception message.
506         if (Proxy.isProxyClass(c))
507             c = c.getInterfaces()[0];  // array "can't be empty"
508         throw new IllegalArgumentException("Illegal type for annotation " +
509                 "element using @DescriptorKey: " + c.getName());
510     }
511 
512     // This must be consistent with the check for duplicate field values in
513     // ImmutableDescriptor.union.  But we don't expect to be called very
514     // often so this inefficient check should be enough.
equals(Object x, Object y)515     private static boolean equals(Object x, Object y) {
516         return Arrays.deepEquals(new Object[] {x}, new Object[] {y});
517     }
518 
519     /**
520      * Returns the XXMBean interface or null if no such interface exists
521      *
522      * @param c The interface to be tested
523      * @param clName The name of the class implementing this interface
524      */
implementsMBean(Class<T> c, String clName)525     private static <T> Class<? super T> implementsMBean(Class<T> c, String clName) {
526         String clMBeanName = clName + "MBean";
527         if (c.getName().equals(clMBeanName)) {
528             return c;
529         }
530         Class<?>[] interfaces = c.getInterfaces();
531         for (int i = 0;i < interfaces.length; i++) {
532             if (interfaces[i].getName().equals(clMBeanName) &&
533                 (Modifier.isPublic(interfaces[i].getModifiers()) ||
534                  ALLOW_NONPUBLIC_MBEAN)) {
535                 return Util.cast(interfaces[i]);
536             }
537         }
538 
539         return null;
540     }
541 
elementFromComplex(Object complex, String element)542     public static Object elementFromComplex(Object complex, String element)
543     throws AttributeNotFoundException {
544         try {
545             if (complex.getClass().isArray() && element.equals("length")) {
546                 return Array.getLength(complex);
547             } else if (complex instanceof CompositeData) {
548                 return ((CompositeData) complex).get(element);
549             } else {
550                 // Java Beans introspection
551                 //
552                 Class<?> clazz = complex.getClass();
553                 Method readMethod;
554                 if (JavaBeansAccessor.isAvailable()) {
555                     readMethod = JavaBeansAccessor.getReadMethod(clazz, element);
556                 } else {
557                     // Java Beans not available so use simple introspection
558                     // to locate method
559                     readMethod = SimpleIntrospector.getReadMethod(clazz, element);
560                 }
561                 if (readMethod != null) {
562                     ReflectUtil.checkPackageAccess(readMethod.getDeclaringClass());
563                     return MethodUtil.invoke(readMethod, complex, new Class<?>[0]);
564                 }
565 
566                 throw new AttributeNotFoundException(
567                     "Could not find the getter method for the property " +
568                     element + " using the Java Beans introspector");
569             }
570         } catch (InvocationTargetException e) {
571             throw new IllegalArgumentException(e);
572         } catch (AttributeNotFoundException e) {
573             throw e;
574         } catch (Exception e) {
575             throw EnvHelp.initCause(
576                 new AttributeNotFoundException(e.getMessage()), e);
577         }
578     }
579 
580     /**
581      * A simple introspector that uses reflection to analyze a class and
582      * identify its "getter" methods. This class is intended for use only when
583      * Java Beans is not present (which implies that there isn't explicit
584      * information about the bean available).
585      */
586     private static class SimpleIntrospector {
SimpleIntrospector()587         private SimpleIntrospector() { }
588 
589         private static final String GET_METHOD_PREFIX = "get";
590         private static final String IS_METHOD_PREFIX = "is";
591 
592         // cache to avoid repeated lookups
593         private static final Map<Class<?>,SoftReference<List<Method>>> cache =
594             Collections.synchronizedMap(
595                 new WeakHashMap<Class<?>,SoftReference<List<Method>>> ());
596 
597         /**
598          * Returns the list of methods cached for the given class, or {@code null}
599          * if not cached.
600          */
getCachedMethods(Class<?> clazz)601         private static List<Method> getCachedMethods(Class<?> clazz) {
602             // return cached methods if possible
603             SoftReference<List<Method>> ref = cache.get(clazz);
604             if (ref != null) {
605                 List<Method> cached = ref.get();
606                 if (cached != null)
607                     return cached;
608             }
609             return null;
610         }
611 
612         /**
613          * Returns {@code true} if the given method is a "getter" method (where
614          * "getter" method is a public method of the form getXXX or "boolean
615          * isXXX")
616          */
isReadMethod(Method method)617         static boolean isReadMethod(Method method) {
618             // ignore static methods
619             int modifiers = method.getModifiers();
620             if (Modifier.isStatic(modifiers))
621                 return false;
622 
623             String name = method.getName();
624             Class<?>[] paramTypes = method.getParameterTypes();
625             int paramCount = paramTypes.length;
626 
627             if (paramCount == 0 && name.length() > 2) {
628                 // boolean isXXX()
629                 if (name.startsWith(IS_METHOD_PREFIX))
630                     return (method.getReturnType() == boolean.class);
631                 // getXXX()
632                 if (name.length() > 3 && name.startsWith(GET_METHOD_PREFIX))
633                     return (method.getReturnType() != void.class);
634             }
635             return false;
636         }
637 
638         /**
639          * Returns the list of "getter" methods for the given class. The list
640          * is ordered so that isXXX methods appear before getXXX methods - this
641          * is for compatibility with the JavaBeans Introspector.
642          */
getReadMethods(Class<?> clazz)643         static List<Method> getReadMethods(Class<?> clazz) {
644             // return cached result if available
645             List<Method> cachedResult = getCachedMethods(clazz);
646             if (cachedResult != null)
647                 return cachedResult;
648 
649             // get list of public methods, filtering out methods that have
650             // been overridden to return a more specific type.
651             List<Method> methods =
652                 StandardMBeanIntrospector.getInstance().getMethods(clazz);
653             methods = MBeanAnalyzer.eliminateCovariantMethods(methods);
654 
655             // filter out the non-getter methods
656             List<Method> result = new LinkedList<Method>();
657             for (Method m: methods) {
658                 if (isReadMethod(m)) {
659                     // favor isXXX over getXXX
660                     if (m.getName().startsWith(IS_METHOD_PREFIX)) {
661                         result.add(0, m);
662                     } else {
663                         result.add(m);
664                     }
665                 }
666             }
667 
668             // add result to cache
669             cache.put(clazz, new SoftReference<List<Method>>(result));
670 
671             return result;
672         }
673 
674         /**
675          * Returns the "getter" to read the given property from the given class or
676          * {@code null} if no method is found.
677          */
getReadMethod(Class<?> clazz, String property)678         static Method getReadMethod(Class<?> clazz, String property) {
679             if (Character.isUpperCase(property.charAt(0))) {
680                 // the property name must start with a lower-case letter
681                 return null;
682             }
683             // first character after 'get/is' prefix must be in uppercase
684             // (compatibility with JavaBeans)
685             property = property.substring(0, 1).toUpperCase(Locale.ENGLISH) +
686                 property.substring(1);
687             String getMethod = GET_METHOD_PREFIX + property;
688             String isMethod = IS_METHOD_PREFIX + property;
689             for (Method m: getReadMethods(clazz)) {
690                 String name = m.getName();
691                 if (name.equals(isMethod) || name.equals(getMethod)) {
692                     return m;
693                 }
694             }
695             return null;
696         }
697     }
698 }
699