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. :)

13 megjegyzés:

  1. Hi Patrik,

    great and useful solution! Hopefully Oracle will take a look and implement more "state of the art techniques" into the ADF product ;)

    VálaszTörlés
  2. Hi Patrik,

    On a related note, check out ER 12320395(JDEVELOPER SHOULD PROVIDE SYNTAX HELP USING GENERIC IN ADF BC)

    Thanks

    VálaszTörlés
  3. perfect explanation about java programming .its very useful.thanks for your valuable information.best java institute in chennai | best java training in velachery

    VálaszTörlés
  4. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a Java developer learn from Java Training in Chennai. or learn thru Java Online Training India . Nowadays Java has tons of job opportunities on various vertical industry.

    VálaszTörlés
  5. Thank you for this post. Thats all I are able to say. You most absolutely have built this blog website into something speciel. You clearly know what you are working on, youve insured so many corners.thanks
    Digital Marketing Training in Chennai

    Digital Marketing Training in Bangalore

    digital marketing training in tambaram

    digital marketing training in annanagar

    VálaszTörlés
  6. Great post! I am actually getting ready to across this information, It’s very helpful for this blog.Also great with all of the valuable information you have Keep up the good work you are doing well.
    Digital Marketing online training

    full stack developer training in pune

    full stack developer training in annanagar

    full stack developer training in tambaram

    VálaszTörlés
  7. A universal message I suppose, not giving up is the formula for success I think. Some things take longer than others to accomplish, so people must understand that they should have their eyes on the goal, and that should keep them motivated to see it out til the end.
    python training institute in chennai
    python training in Bangalore
    python training in pune

    VálaszTörlés
  8. Thank you for allowing me to read it, welcome to the next in a recent article. And thanks for sharing the nice article, keep posting or updating news article.
    Blueprism training in tambaram

    Blueprism training in annanagar

    Blueprism training in velachery

    VálaszTörlés
  9. Nice tutorial. Thanks for sharing the valuable information. it’s really helpful. Who want to learn this blog most helpful. Keep sharing on updated tutorials…
    Data Science training in kalyan nagar
    Data Science training in OMR
    selenium training in chennai
    Data Science with Python training in chenni
    Data science training in velachery

    VálaszTörlés
  10. This is such a good post. One of the best posts that I\'ve read in my whole life. I am so happy that you chose this day to give me this. Please, continue to give me such valuable posts. Cheers!
    java training in jayanagar | java training in electronic city

    java training in chennai | java training in USA

    VálaszTörlés
  11. Your very own commitment to getting the message throughout came to be rather powerful and have consistently enabled employees just like me to arrive at their desired goals.

    angularjs Training in bangalore

    angularjs Training in btm

    angularjs Training in electronic-city

    angularjs online Training

    angularjs Training in marathahalli

    VálaszTörlés
  12. Whoa! I’m enjoying the template/theme of this website. It’s simple, yet effective. A lot of times it’s very hard to get that “perfect balance” between superb usability and visual appeal. I must say you’ve done a very good job with this.

    AWS Training in Bangalore | Amazon Web Services Training in bangalore , india

    AWS Training in pune | Amazon Web Services Training in Pune, india

    AWS Training in Chennai|Amazon Web Services Training in Chennai,India


    aws online training and certification | amazon web services online training ,india

    VálaszTörlés