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:
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() are automatically forwarded, because an implementation of
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:
public class ClassEx
// ...
public MethodEx getMethod(String name, ClassEx... parameterTypes)
throws NoSuchMethodException, SecurityException {
Class[] pt = new Class[parameterTypes.length];
for(int i=0; i
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:
class Conversions {
public static Class[] convertToClassArray(ClassEx[] input) {
Class[] output = new Class[input.length];
for(int i=0; i
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
annotation to specify a class that contains the conversion methods:
@DelegateTo(target=java.lang.Class.class, conversions=Conversions.class)
public class ClassEx
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:
@DelegateTo(target=java.lang.Class.class,
input={InputConversions.class},
output={OutputConversions.class})
public class ClassEx
class InputConversions {
public static Class[] convertToClassArray(ClassEx[] input) {
Class[] output = new Class[input.length];
for(int i=0; i
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.