Automatic Delegation Would Be Nice

Last night I finished the core of the extended Class implementation supporting annotations with subtyping. I load default values, but I punted on the order of the members in the toString() method. As far as I know, it is implementation-specific anyway.

Today I decided not to go to the office, but instead work from home. My workstation here has a better setup for coding, and I felt I was at the office long enough yesterday and that my presence wasn’t necessary for grading anymore, because as far as I know the missing grad TA had returned.

So I spent most of today writing extended versions of the Constructor, Method, Field and Package classes. I was able to factor most common code out, so implementing the core functionality wasn’t hard. Now I’m working on the supporting methods, e.g. those that allow you to get a Method from a Class object, without having to refer back to Java’s original Class object.

In essence, I’m creating gigantic wrappers for Java’s classes, and I’m forwarding each call to the corresponding method in the original class. It would be nice if this could be automated, and in fact, I think that could easily be done, again through annotations and bytecode rewriting. Consider the following listing:

1
2
3
4
5
6
7
8
9
10
11
12
class A {
  public void foo() { }
  public void bar() { }
}

@DelegateTo(A.class)
class B { }

@DelegateTo(A.class)
class C {
  public void bar() { }
}

The A class defines two methods, foo() and bar(). Classes B and C are then annotated with @DelegateTo(A.class). When the bytecode rewriting framework sees this annotation, it automatically adds all of A‘s methods to B and C and generates bytecode to forward the calls. In the case of C, only calls to foo()</cod> are automatically forwarded, because an implementation of <code inline="true" escaped="true">bar() has been provided.

The problem with this is that many times some processing has to be done before and after the delegation. For example, in the public MethodEx getMethod(String name, ClassEx... parameterTypes)() method of my extended Class class, I have to extract the original java.lang.Class objects from the elements in the paramTypes array, then pass the new array to the original method, and then finally create a new extended MethodEx instance using the return value from the forwarded call:

1
2
3
4
5
6
7
8
9
10
11
12
public class ClassEx<T> {
  // ...
  public MethodEx getMethod(String name, ClassEx... parameterTypes)
    throws NoSuchMethodException, SecurityException {
    Class[] pt = new Class[parameterTypes.length];
    for(int i=0; i<pt.length; ++i) {
      pt[i] = parameterTypes[i].java;
    }
    return new MethodEx(java.getMethod(name, pt));
  }
  // ...
}

This kind of processing is very common. Even though I’m writing several dozen methods that delegate, there are only about half a dozen ways to process data. This made me think of C++’s conversion operators (e.g. operator int() { return ...; }), which are basically user-designed casts with the ability to perform processing.

In the above example, the two processing methods I need are from ClassEx[] to Class[], and from Method to MethodEx. I could write a class with static methods to perform these conversions:

1
2
3
4
5
6
7
8
9
10
11
12
class Conversions {
  public static Class[] convertToClassArray(ClassEx[] input) {
    Class[] output = new Class[input.length];
    for(int i=0; i<pt.length; ++i) {
      output[i] = input[i].java;
    }
    return output;
  }
  public static MethodEx convertToMethodEx(Method input) {
    return new MethodEx(input);
  }
}

The names aren’t really important, because the bytecode rewriting framework would look for a method with the correct input and output types for the conversion it has to perform. Some care needs to be taken, though, because Java doesn’t allow overloading if only the return type differs. That’s why I have suffixed the methods above with a description of the return type.

Now I can change the @DelegateTo<code inline="true" escaped="true"> annotation to specify a class that contains the conversion methods:

<code lang="java">@DelegateTo(target=java.lang.Class.class, conversions=Conversions.class)
public class ClassEx<T> { }

The bytecode rewriting framework will add all methods from java.lang.Class to ClassEx, but all occurrences of Class[] will be replaced by ClassEx[], and all occurrences of Method will be replaced by MethodEx. Instead of having a method public Method getMethod(String name, Class... parameterTypes)() like java.lang.Class, ClassEx will have a method public MethodEx getMethod(String name, ClassEx... parameterTypes)(). The String name is not processed in any way, because there is no conversion method from String to String, but Class... parameterTypes will be changed to ClassEx... parameterTypes, and the Class[] convertToClassArray(ClassEx[] input) will be used for the conversion. The return value will be changed from Method to MethodEx, and the MethodEx convertToMethodEx(Method input) method will be used for the conversion.

It probably makes sense to allow an array of classes with conversion methods, since the conversion methods may be spread out across several classes. The framework will have to check that there is no ambiguity as to which conversion method is used, i.e. there may not be two or more methods in all specified classes with the same input and output types. It may also be a good idea to allow different conversion methods for input and output. That would lead us to a syntax like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@DelegateTo(target=java.lang.Class.class,
            input={InputConversions.class},
            output={OutputConversions.class})
public class ClassEx<T> { }

class InputConversions {
  public static Class[] convertToClassArray(ClassEx[] input) {
    Class[] output = new Class[input.length];
    for(int i=0; i<pt.length; ++i) {
      output[i] = input[i].java;
    }
    return output;
  }
}

class OutputConversions {
  public static MethodEx convertToMethodEx(Method input) {
    return new MethodEx(input);
  }
}

Considering that this will handle the conversions for more than one method, I think the effort would well be worth it.

There’s still a problem with type checking, of course: Since classes for which the forwarding methods should be generated automatically don’t actually contain the methods in the source code, the Java compiler will emit errors whenever the methods are used. Compiling should therefore probably happen in three phases: First, all classes except the ones to be generated automatically are compiled; second, the automatic bytecode rewriting is done; third, all classes except the ones generated automatically are compiled again.

Share

About Mathias

Software development engineer. Principal developer of DrJava. Recent Ph.D. graduate from the Department of Computer Science at Rice University.
This entry was posted in Research, xajavac. Bookmark the permalink.

Leave a Reply