Uploaded image for project: 'Tapestry'
  1. Tapestry
  2. TAPESTRY-2053

Generics support for prop binding (PropertyConduitSource)

    XMLWordPrintableJSON

Details

    • Improvement
    • Status: Closed
    • Major
    • Resolution: Duplicate
    • 5.0.8
    • None
    • None
    • None

    Description

      I've tried to make a base class for ListPages.
      Most ListPages have a grid with entities from Hibernate,
      and I ended up creating same sets of methods for each.
      getter: HibernateEntityDataSource
      property: currentRow

      the base ListPage is like this:
      ---------------------------------------------
      public abstract class ListPage<T> {

      private Class<T> persistentClass;
      private T current;

      public ListPage()

      { this.persistentClass = (Class<T>) ((ParameterizedType) getClass() .getGenericSuperclass()).getActualTypeArguments()[0]; }

      @Inject
      private Session _session;

      public HibernateEntityDataSource<T> getList()

      { HibernateEntityDataSource<T> documents = HibernateEntityDataSource.create(_session,persistentClass); return documents; }

      public T getCurrent()

      { return current; }

      public void setCurrent(T current)

      { this.current = current; }

      }
      ---------------------------------------------

      I can then create a concrete page like this:
      ---------------------------------------------
      public class ListDocument extends ListPage<Document>{

      }
      ---------------------------------------------

      the template is simple..
      ---------------------------------------------
      <t:grid source="list" rowsPerPage="15" t:id="results" remove="vat,note" row="current"/>
      ---------------------------------------------

      Things are fine until I try to customize table output and use binding expressions.
      ---------------------------------------------
      <t:parameter name="ammountCell">
      ${current.ammount} ${current.currency}
      </t:parameter>
      ---------------------------------------------

      Tapestry throws exception stating that property "ammount" can not be found for java.lang.Object.
      This is no surprise because erasure of generic info. for the method "getCurrent" return type is java.lang.Object.
      One solution is to override offending methods:
      ---------------------------------------------
      @Override public Document getCurrent()

      {return super.getCurrent();}

      @Override public void setCurrent(Document current)

      {super.setCurrent(current);}

      ---------------------------------------------
      but this is useless nad makes base page obsolete

      Generics are no fun for to use with reflection, but the actual type can in be calculated.

      ****************************************************

      I modified PropertyConduitSource just to see if it is feasible.
      It covers only my use case and maybe some more, and it should report errors for unmanagable cases.
      The implementation was filled with traps and was a bit of headache to get it running.
      The getActualType method should be moved to a utility class.... and it will probably get more complicated
      to handle all cases.

      I added a inner class:
      --------------------------
      private static class MethodInfo{
      Method method;
      Class methodType;
      public MethodInfo(Method method, Class methodType)

      { this.method = method; this.methodType = methodType; }

      }
      --------------------------
      just to return edequate info for the actualType from buildGetter and buildSetter

      added method:
      --------------------------
      private Class getActualType(Class type, Type genericReturnTypeDef) {
      Class actualType = null;

      // If a primitive type, convert to wrapper type
      if(genericReturnTypeDef instanceof TypeVariable){
      TypeVariable[] typeParameters = type.getSuperclass().getTypeParameters();
      System.out.println("#"+type.getName());
      Type[] actualTypeParameters = ((ParameterizedType) type.getGenericSuperclass()).getActualTypeArguments();
      TypeVariable genericReturnType = (TypeVariable) genericReturnTypeDef;
      for(int j=0; j<typeParameters.length; j++)

      { if(typeParameters[j].equals(genericReturnType)) actualType = (Class) actualTypeParameters[j]; }

      }else if(genericReturnTypeDef instanceof Class)

      { actualType = (Class) genericReturnTypeDef; }

      else if(genericReturnTypeDef instanceof ParameterizedType)

      { actualType = (Class) ((ParameterizedType) genericReturnTypeDef).getRawType(); }

      else

      { throw new RuntimeException("Could not handle generic info of type "+genericReturnTypeDef+" : "+genericReturnTypeDef.getClass().getName()); }

      return actualType;
      }
      --------------------------
      to reconstruct type info from generic declaration
      it covers only basics and only reconstructs first level(further info is ignored)
      places where method.getReturnType() and getParameterTypes() were used were replaced with
      a call to getActualType

      $w syntax for javassist had to be used conditionaly since it would generate
      VerifyError: Incompatible object argument for function call
      I had no time to get to the bottom of it, the error occured only in the
      case when expression came from generic type def. This is only what I've
      observed, I haven made tests to say it definitely.
      a similar erro happend for people using normal compiler
      http://forum.java.sun.com/thread.jspa?threadID=398785&forumID=316
      so it may not even be javassist issue.

      Attachments

        Issue Links

          Activity

            People

              hlship Howard Lewis Ship
              hrgdavor Davor Hrg
              Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: