2012. március 13., kedd

Applying Generics to ADF BC View Objects

Oh well. This is my first post intended for the audience, and not just as a note for myself.
Instead of long introductions, let's get into it immediately. ;)

So this post is about why and how to apply Java generics to ADF BC, more specifically to the view objects, in order to work with them programmatically in a safer and more convenient way.

The problem

If you are working with view objects and view rows programmatically when using ADF BC, e.g. working with rows which are retrieved from a view object, then you may have a lot of code similar to the following snippets.

DepartmentsViewImpl deptVO = appModule.getDepartmentsView1();
while (deptVO.hasNext) {
    // Note that you have to cast the row even though the VO is a DepartmentsViewImpl
    // which always returns DepartmentsViewRow instances. 
    DepartmentsViewRow dept = (DepartmentsViewRow)deptVO.next();
    // Do something with dept, e.g. you want to use the generated
    // type-safe methods on the row.
    Number deptID = dept.getId();
    ...
}

Key key = ...;
// You cannot even do this, because the returned object will always be an array of Rows.
DepartmentsViewRow[] foundDepts = (DepartmentsViewRow[])deptVO.findByKey(key, 100);
// Instead you have to cast each Row separately...
Row[] foundDepts = deptVO.findByKey(key, 100);
for (Row row : foundDepts) {
    DepartmentsViewRow dept = (DepartmentsViewRow)row;
    Number deptID = dept.getId();
    ...
}

Here basically you don't have too much compile-time type-safety, and it is error-prone in the sense that you always have to take care to do the proper casts. Moreover, after a while it just feels inconvenient and cumbersome, and can get really annoying. At some point you will have to cast the Row instances to the actual row types if you want to code to the interfaces.

Wouldn't it be nice to write just something like this?

DepartmentsViewImpl deptVO = appModule.getDepartmentsView1();
while (deptVO.hasNext) {
    DepartmentsViewRow dept = deptVO.next();
    // Do something with dept
    Number deptID = dept.getId();
    ...
}

Key key = ...;
DepartmentsViewRow[] foundDepts = deptVO.findByKey(key, 100);
for (DepartmentsViewRow dept : foundDepts) {
    // Do something with dept
    Number deptID = dept.getId();
    ...
}


So you might wonder:
  • Why do I always have to cast oracle.jbo.Row to my row class when iterating through row sets, finding rows etc.? Is there no way to avoid this cumbersome code?
  • Why can't I have more compile-time safety when using specific types of view objects? Why is it the developer and not the framework or the design-time tools that need to take care of proper casts?
  • Why is there no (compile-time) relation between the VO and Row types, even if in most cases they have a one-to-one relation?
  • And here we arrive to the question in general: Why is there no Java generics applied to ADF BC at all? Even though the view objects seem a very straightworward candidate to use generics. (Well, most of the row related classes seem good candidates, e.g. Row, ViewObject, RowIterator, RowSet, RowSetIterator. But for other than the ViewObject, I cannot see an easy solution like the one desribed below.)

The idea

Well, until generifying the ADF BC code eventually happens (if ever), we can use a simple workaround to have generic methods in our view objects.
The idea is to have a generic ViewObject class with the row type as type parameter, and to override the VO methods to return instances of our parameterized view row type. Thanks to covariant return types introduced in Java 5, we can actually do this.

Here are the main steps to achieve this:
  1. Create a generic ViewObject interface extension with overridden methods (using covariant return types)
  2. Extend the framework-provided ViewObjectImpl to implement our generic interface (and implement the overridden methods appropriately)
  3. In the actual VO classes (e.g. DepartmentsViewImpl) extend this base class using the appropriate (row) type parameter

Note that using the interface in the first step is optional; and doing the implementation in a separate base class helps us avoid coding it in every actual VO class.

Details of the solution

The steps are detailed below including code snippets.

Create our custom generic interface called GenericViewObject extending the ViewObject interface like this:
public interface GenericViewObject<T extends Row> extends ViewObject {

    @Override
    public T getCurrentRow();

    @Override
    public T next();

    @Override
    public T[] findByKey(Key key, int i);

    @Override
    public T[] getAllRowsInRange();

    // similar overridden signatures for all the methods having return types of Row or Row[]
    ...

}

In the corresponding implementation class (the ViewObjectImpl extension called GenericViewObjectImpl) we can safely cast the Row instances to the actual row type (T) as we know that the VO implementation will return instances of that specific row type.

However, we have a problem when returning arrays of Rows: we cannot cast them to T[], as no matter what the type of the contained Rows is, this will always be just an array of Rows, i.e. a Row[] instance. So we have to find a way to convert the Row[] instance to a T[] instance. The problem with this is that we have to create a new array using reflection (Array.newInstance()), because you cannot instantiate a generic array. In order to do that, we have to know row class, but (in almost all cases) there is no way to get the actual type argument of a generic class at runtime. However, there is one exception to this rule: we can get the actual type arguments of a generic superclass from a subclass. And fortunately this is exactly the case when extending our generic VO base class.

See the code below on how to do that, and store the class token in order to use it later when instantiating new arrays of our type parameter.

public abstract class GenericViewObjectImpl<T extends Row> extends ViewObjectImpl implements GenericViewObject<T> {

    private Class<T> rowClass;
    
    public GenericViewObjectImpl(String string, ViewDefImpl viewDefImpl) {
        super(string, viewDefImpl);
        storeClassToken();
    }

    public GenericViewObjectImpl() {
        super();
        storeClassToken();
    }
    
    /**
     * Get the class token of the actual type argument and store it for later use.
     */
    private final void storeClassToken() {
        // When called from a subclass, this will be GenericViewObjectImpl
        // containing the type parameter.
        ParameterizedType superClass = (ParameterizedType)this.getClass().getGenericSuperclass();
        // The first and only type parameter is the row class.
        this.rowClass = (Class<T>)superClass.getActualTypeArguments()[0];
    }

    /**
     * Convert an array of Rows to an array of the generic type of this class.
     * 
     * @param rows array of rows
     * @return generic array of rows
     */
    private final T[] convertToGenericArray(Row[] rows) {
        if (rows==null) {
            // khm... array-valued method returns null,
            // but it's what super does if we have null here...
            return null;
        }
        T[] genericRows = (T[])Array.newInstance(this.rowClass, rows.length);
        for (int i = 0; i < rows.length; i++) {
            genericRows[i] = (T)rows[i];
        }
        return genericRows;
    }

    @Override
    public T getCurrentRow() {
        return (T)super.getCurrentRow();
    }

    @Override
    public T next() {
        return (T)super.next();
    }

    @Override
    public T[] findByKey(Key key, int i) {
        return convertToGenericArray(super.findByKey(key, i));
    }

    @Override
    public T[] getAllRowsInRange() {
        return convertToGenericArray(super.getAllRowsInRange());
    }

    // similarly for all the other methods
    ...

}

That's all.

Now all you have to do is replace the extends clauses in your view objects, e.g. instead of
public class DepartmentsViewImpl extends ViewObjectImpl {}
...you can write:
public class DepartmentsViewImpl extends GenericViewObjectImpl<DepartmentsViewRow> {}

After this you can start accessing your view rows from your view objects as described above, e.g.:
DepartmentsViewImpl deptVO = appModule.getDepartmentsView1();
while (deptVO.hasNext) {
    DepartmentsViewRow dept = deptVO.next();
    // Do something with dept
    Number deptID = dept.getId();
    ...
}

I hope this will be useful for some of you, or at least to start some discussion about Java generics regarding ADF BC. Your comments are much appreciated.

Cheers. :)