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