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