Announcement Announcement Module
Collapse
No announcement yet.
BeanWrapper trampling generics? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • BeanWrapper trampling generics?

    I'm getting this problem in the web layer, but I think it's related to the container itself, which is why I'm posting here.

    All this is using Spring Framework 2.0.6.

    I'm writing a couple of SimpleFormControllers who need very similar command objects. Being a lazy man I figured I'd write a base class for all these classes using the spiffy Java5 generics.

    The generic superclass looks like this:

    Code:
    public class GenericCommand<T> {
        private T add;
        private boolean change;
    
        GenericCommand() {}
    
        public void setAdd(T a) { add = a; }
        public T    getAdd()    { return add; }
    
        public void    setChange(boolean c) { change = c; }
        public boolean  isChange()          { return change; }
    }
    One of the subclasses of GenericCommand is CourseCommand:

    Code:
    public class CourseCommand extends GenericCommand<Course> {
        private String changeSemester;
    
        CourseCommand() {
            super();
            setAdd(new Course());
        }
    
        public void   setChangeSemester(String s) { changeSemester = s; }
        public String getChangeSemester()         { return changeSemester; }
    }
    Now, this seems to be all nice and spiffy, until I try submitting the form. That gets me this error when the validator tries to get the attribute add from CourseCommand: java.lang.NoSuchMethodError: uio.ifi.joly.web.command.CourseCommand.getAdd()Lui o/ifi/joly/domain/Course

    From what I can tell it looks like Spring has reflected a new getAdd() method on top of my old one, with the return type Object, clobbering the type erasure generated by the compiler, and making the generics go up in smoke. Is this a known issue, and if so is there a way to stop Spring from breaking the generic functionality?

  • #2
    That gets me this error when the validator tries to get the attribute add from CourseCommand: java.lang.NoSuchMethodError: uio.ifi.joly.web.command.CourseCommand.getAdd()Lui o/ifi/joly/domain/Course
    Can you post your validator code?

    clobbering the type erasure generated by the compiler, and making the generics go up in smoke
    I'm not sure how one clobbers type erasure

    Comment


    • #3
      Similar issue

      Hi

      I believe that I has similar issue where the BeanWrapper.getPropertyType() always returns Object...

      Code:
      public class Super1<I> {
          I id;
      
          public Super1(I id) {
              this.id = id;
          }
      
          public I getId() {
              return id;
          }
      }
      and my test:
      Code:
      Super1<Integer> super1 = new Super1<Integer>(123);
      BeanWrapper bw2 = new BeanWrapperImpl(super1);
      System.out.println("id is of type:" + bw2.getPropertyType("id"));
      System.out.println("bw access to id is of type:" + bw2.getPropertyValue("id").getClass());
      System.out.println("direct access to id is of type:" + super1.getId().getClass());
      The first call returns java.lang.OBJECT, which is incorrect I'd say. the other 2 calls return java.lang.Integer.

      The issue is that the use of BeanWrapper.getPropertyType() is deeply embedded in many libraries, including the Spring Rich Client one that I am using and this causes havoc!

      Any suggestion / patch?

      many thanks

      Benoit

      Comment


      • #4
        It is direct consequence of type erasure (see 4.6 in the Java langauge specification, "The erasure of a parameterized type (4.5) G<T1, ... ,Tn> is |G|."). So, at runtime class Super1<Integer> is represented just as Super1 and any reflection calls can not see that it was parametrized by Integer,

        You have to blame Java language itself, not Spring in this case.

        Regards,
        Oleksandr

        Originally posted by benoitx View Post
        Hi

        I believe that I has similar issue where the BeanWrapper.getPropertyType() always returns Object...

        Code:
        public class Super1<I> {
            I id;
        
            public Super1(I id) {
                this.id = id;
            }
        
            public I getId() {
                return id;
            }
        }
        and my test:
        Code:
        Super1<Integer> super1 = new Super1<Integer>(123);
        BeanWrapper bw2 = new BeanWrapperImpl(super1);
        System.out.println("id is of type:" + bw2.getPropertyType("id"));
        System.out.println("bw access to id is of type:" + bw2.getPropertyValue("id").getClass());
        System.out.println("direct access to id is of type:" + super1.getId().getClass());
        The first call returns java.lang.OBJECT, which is incorrect I'd say. the other 2 calls return java.lang.Integer.

        The issue is that the use of BeanWrapper.getPropertyType() is deeply embedded in many libraries, including the Spring Rich Client one that I am using and this causes havoc!

        Any suggestion / patch?

        many thanks

        Benoit

        Comment


        • #5
          Not entirely true.

          Hi Oleksandr

          Thanks for your reply. It is not entirely impossible as highlighted in 2 articles:
          http://www.ibm.com/developerworks/ja...-cwt11085.html
          http://www.artima.com/weblogs/viewpo...?thread=208860

          This require some Java 1.5 code (but it could be made backward compatible by some tools). In any case, I'd think that Spring could either come up with a spring-core-jdk15 using these technologies or define some kind of Annotation that would make the BeanWrapper able to find out more about the PropertyType (why not using getPropertyValue().getClass() as the first attempt).

          For instance in the enclosed code that shows it is possible to find out the generic type of a property.

          Class net.objectlab.springtest.GenericTest details:
          intSuper2 is of parameterized type
          net.objectlab.springtest.GenericTest$Super1
          using types (java.lang.Integer)
          Class net.objectlab.springtest.GenericTest$Super1 details:
          id is of type java.lang.Object

          Not a 'clean' solution of course, but it shows that it may not be impossible I'd think. Especially if the developers were to adhere to a convention of some sort...

          My 0.02 suggestion,

          Benoit

          Code:
          import java.lang.reflect.Field;
          import java.lang.reflect.GenericArrayType;
          import java.lang.reflect.Modifier;
          import java.lang.reflect.ParameterizedType;
          import java.lang.reflect.Type;
          import java.util.HashSet;
          
          import org.springframework.beans.BeanWrapper;
          import org.springframework.beans.BeanWrapperImpl;
          import org.springframework.binding.form.FormModel;
          import org.springframework.richclient.form.FormModelHelper;
          
          public class GenericTest {
          private Super1<Integer> intSuper2;
          
          public Super1<Integer> getIntSuper2() {
          return intSuper2;
          }
          
          public void setIntSuper2(Super1<Integer> intSuper2) {
          this.intSuper2 = intSuper2;
          }
          
          public static void main(String[] a) {
          try {
              analyze("", GenericTest.class);
          } catch (SecurityException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }
          }
          
          private static HashSet<String> s_processed = new HashSet<String>();
          
          private static void describe(String lead, Field field) {
          
          // get base and generic types, check kind
          Class<?> btype = field.getType();
          Type gtype = field.getGenericType();
          if (gtype instanceof ParameterizedType) {
          
              // list basic parameterized type information
              ParameterizedType ptype = (ParameterizedType) gtype;
              System.out.println(lead + field.getName() + " is of parameterized type");
              System.out.println(lead + ' ' + btype.getName());
          
              // print list of actual types for parameters
              System.out.print(lead + " using types (");
              Type[] actuals = ptype.getActualTypeArguments();
              for (int i = 0; i < actuals.length; i++) {
          	if (i > 0) {
          	    System.out.print(" ");
          	}
          	Type actual = actuals[i];
          	if (actual instanceof Class) {
          	    System.out.print(((Class) actual).getName());
          	} else {
          	    System.out.print(actuals[i]);
          	}
              }
              System.out.println(")");
          
              // analyze all parameter type classes
              for (int i = 0; i < actuals.length; i++) {
          	Type actual = actuals[i];
          	if (actual instanceof Class) {
          	    analyze(lead, (Class) actual);
          	}
              }
          
          } else if (gtype instanceof GenericArrayType) {
          
              // list array type and use component type
              System.out.println(lead + field.getName() + " is array type " + gtype);
              gtype = ((GenericArrayType) gtype).getGenericComponentType();
          
          } else {
          
              // just list basic information
              System.out.println(lead + field.getName() + " is of type " + btype.getName());
          }
          
          // analyze the base type of this field
          analyze(lead, btype);
          }
          
          private static void analyze(String lead, Class<?> clas) {
          
          // substitute component type in case of an array
          if (clas.isArray()) {
              clas = clas.getComponentType();
          }
          
          // make sure class should be expanded
          String name = clas.getName();
          if (!clas.isPrimitive() && !clas.isInterface() && !name.startsWith("java.lang.") && !s_processed.contains(name)) {
          
              // print introduction for class
              s_processed.add(name);
              System.out.println(lead + "Class " + clas.getName() + " details:");
          
              // process each field of class
              String indent = lead + ' ';
              Field[] fields = clas.getDeclaredFields();
              for (int i = 0; i < fields.length; i++) {
          	Field field = fields[i];
          	if (!Modifier.isStatic(field.getModifiers())) {
          	    describe(indent, field);
          	}
              }
          }
          }

          Comment

          Working...
          X