Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6807471

Introspection depends on the (inconsistent) order of Class.getDeclaredMethods()

    XMLWordPrintable

Details

    • x86
    • windows_xp

    Description

      FULL PRODUCT VERSION :
      java version "1.6.0_12"
      Java(TM) SE Runtime Environment (build 1.6.0_12-b04)
      Java HotSpot(TM) Client VM (build 11.2-b01, mixed mode, sharing)

      ADDITIONAL OS VERSION INFORMATION :
      Windows XP Professional SP2 [Version 5.1.2600]

      A DESCRIPTION OF THE PROBLEM :
      The result of Introspector.getBeanInfo depends on the order of the target class's methods returned by Class.getDeclaredMethods. The documentation for this declares that no particular order can be expected, leading to inconsistent behaviour.

      Variants of this issue have apparently been raised and fixed multiple times, see e.g. Bug 4477877 "Introspection depends on method order, method order depends on class load order".

      In this case, the order of Method[] objects returned by Introspector.getPublicDeclaredMethods determines which of the two "getNumber()" getters is associated with the "number" bean property.

      For one of these, a matching setter exists, and thus a writeMethod is found. For the other, no matching setter exists, and writeMethod is null.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the attached sample code. Note that the sample code *simulates* differing return order in Class.getDeclaredMethods() by setting the simulated result in the Introspector's "declaredMethodCache".

      This is not ideal, but seemed to be the most simple way of providing a test case without relying on the JVM class loading context.

        From inspection of Introspector.getPublicDeclaredMethods it is clear that the Method[] array cached in line 1347 has the same order as that returned by fclz.getDeclaredMethods() is 1333, so the test should produce the same behaviour if the cache were empty and fclz.getDeclaredMethods were called.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Consistent results for both passes.
      ACTUAL -
      class, read method: public final native java.lang.Class java.lang.Object.getClass(), write method: null
      number, read method: public java.lang.Number com.qrmedia.bugs.introspector.IntrospectorBugDemo$Child.getNumber(), write method: public void com.qrmedia.bugs.introspector.IntrospectorBugDemo$Parent.setNumber(java.lang.Number)
      class, read method: public final native java.lang.Class java.lang.Object.getClass(), write method: null
      number, read method: public java.lang.Long com.qrmedia.bugs.introspector.IntrospectorBugDemo$Child.getNumber(), write method: null

      Note the different "write method" in both cases!

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      package com.qrmedia.bugs.introspector;

      import java.beans.BeanInfo;
      import java.beans.Introspector;
      import java.beans.PropertyDescriptor;
      import java.lang.ref.SoftReference;
      import java.lang.reflect.Field;
      import java.lang.reflect.Method;
      import java.util.Map;

      /**
       * Demonstrates a bug in the {@link Introspector}. See <a href="http://bugs.sun.com/view_bug.do?bug_id=4477877">
       * Bug ID: 4477877</a>.
       *
       * @author anph
       * @since 13 Feb 2009
       *
       */
      public class IntrospectorBugDemo {

          public class Parent {
              private Number number;

              public Number getNumber() { return number; }

              public void setNumber(Number number) { this.number = number; }
          }
          
          public class Child extends Parent {
              @Override
              public Long getNumber() { return (Long) super.getNumber(); }
          }
          
          public static void main(String[] args) throws Exception {
              Method[] childMethods = Child.class.getDeclaredMethods();
              forceIntrospectorCache(Child.class, childMethods);
              BeanInfo beanInfo1 = Introspector.getBeanInfo(Child.class);
              
              printDescriptors(beanInfo1.getPropertyDescriptors());
              
              Method temp = childMethods[0];
              childMethods[0] = childMethods[1];
              childMethods[1] = temp;
              
              Introspector.flushCaches();
              forceIntrospectorCache(Child.class, childMethods);
              BeanInfo beanInfo2 = Introspector.getBeanInfo(Child.class);
              
              printDescriptors(beanInfo2.getPropertyDescriptors());
          }
          
          /*
           * One way of reliably controlling the order of methods in
           * Introspector.getPublicDeclaredMethods()'s return value.
           */
          @SuppressWarnings("unchecked")
          private static void forceIntrospectorCache(Class clazz, Method[] methods)
                  throws Exception {
              Field field = Introspector.class.getDeclaredField("declaredMethodCache");
              field.setAccessible(true);
              
              ((Map) field.get(null)).put(Child.class, new SoftReference(methods));
          }
          
          private static void printDescriptors(PropertyDescriptor[] descriptors) {
              
              for (PropertyDescriptor descriptor : descriptors) {
                  System.out.println(descriptor.getName() + ", read method: "
                          + descriptor.getReadMethod() + ", write method: "
                          + descriptor.getWriteMethod());
              }
              
              System.out.println();
          }

      }
      ---------- END SOURCE ----------

      Attachments

        Issue Links

          Activity

            People

              Unassigned Unassigned
              igor Igor Nekrestyanov (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

                Created:
                Updated:
                Imported:
                Indexed: