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