Javassist and Annotations for Interfacing Java to CAL

In order for CAL to interoperate with Java frameworks we need to provide a Java class which delegates method calls to CAL functions. The framework sees only the Java class, and is unaware of the delegation which is taking place.

How we do this depends on a number of factors:

  • How easy it is to hook into the class/object creation process used by the framework.
  • Whether you need a Java class there at compile-time — perhaps you’ll be using a mixture of Java and CAL, so that you need an interface or a class to compile against, or perhaps the only use the framework will make will make of the class is reflectively at runtime, in which case the entire class can be synthesised.

In both cases we want the minimum amount of textual overhead, and the ability to type check our code as early as possible.

The first method I’ll describe uses Javassist:

Javassist provides a high level interface for examining and modifying class files. ‘High level’ because method bodies can be replaced by giving a string of Java code, unlike libraries such as ASM, which work at the level of JVM byte codes.

This technique allows a Java class to have some or all of its methods replaced by calls to CAL functions.

Here’s a trivial Java class:

public class WordCounter {
    private int wordCount = 0;

    public String addWord(String word)
    {
        String[] words = word.split(" ");
        wordCount += words.length;
        return word.toLowerCase();
    }

    public int getWordCount()
    {
        return wordCount;
    }

    public static void main(String[] args)
    {
        WordCounter wc = new WordCounter();
        System.out.println(wc.addWord("Hello World.") +" " +  wc.addWord("Hello another World."));
        System.out.println(wc.getWordCount() + " words.");
    }
}

To convert the addWord function to a call to CAL we do this:

import tdavies.cal.Cal;

public abstract class CalWordCounter {
    ...

    @Cal(workspace="myworkspace.cws", module="TDavies.WordCount", in="wordCount", out="wordCount")
    public abstract String addWord(String word);

    ...
}

The annotation says that the body of the annotated method should be replaced with a call to the CAL function with the same name as the method, defined in the module TDavies.WordCounter which is available in the workspace myworkspace.cws. In addition to the arguments to the method, the function will be passed the value of the wordCount field, and in addition to the return value of the method the function will also return a new value for the wordCount field.

The CAL module looks like this (omitting imports):

addWord :: String -> Int -> (String, Int);
public addWord word count = (toLowerCase word, count + (length $ words word));

In order to get the class modified during the loading process we use the standard Javassist way of running a program using a Javassist Translator:

public class CalRunner {
    public static void main(String[] args) throws Throwable {
        ClassPool pool = ClassPool.getDefault();
        Loader cl = new Loader();
        cl.addTranslator(pool, new CalTranslator());
        if (args.length < 1)
        {
            System.err.println("usage: tdavies.cal.CalRunner <target application class name> [<target application arguments>]");
            System.exit(1);
        }
        String[] newArgs = new String[args.length - 1];
        for (int i = 0; i < newArgs.length; ++i) {
            newArgs[i] = args[i + 1];
        }
        cl.run(args[0], newArgs);
    }
}

The Translator used converts the body to a call to the CAL function.

The sample above is very simple. The following capabilities are also provided:

  • Return values of type Maybe which are Nothing are converted to null.
  • Workspace and module can be specified at the class level, they don’t need to be repeated for each method.
  • An OutputPolicy can be specified for the return type. This is useful when returning an infinite list, as the ITERATOR_OUTPUT_POLICY can be used to allow the Java client to get the values one at a time.

Further enhancements should include:

  • The in and out values should be provided/received in a CAL Record, with field names matching those in the object.
  • Input and output policies for in and out values should be able to be provided.
  • I need to understand whether I should be using an ExecutionContext, and what the implications of caching CAF (Constant Applicative Form) values would be.

This example doesn’t demonstrate the details of using CAL with frameworks, as hooking Javassist into any non-default class loading strategy involves extra work.

The next post will describe using CAL for some of the Tapestry 5 Tutorial

4 Comments

  1. Andrew
    Posted September 24, 2007 at 5:27 pm | Permalink

    As I mentioned before, this is very cool. I like how it is possible to have multiple return values.

    One thing that I noticed, though, is that CALWordCounter.addWord is abstract. How is this class instantiated?

    Also, how are input and output policies used? It seems like it wouldn’t be too hard to specify them as fields in the annotation.

  2. Tom Davies
    Posted September 24, 2007 at 9:12 pm | Permalink

    Good point — if the class is abstract it must be instantiated reflectively, which works when you use a class as a Tapestry page, but not in the example I gave — that would require a concrete class with a dummy body.

    Here’s an example of specifying an output policy:

    @Cal(outputPolicy=ITERATOR) protected abstract Iterator randomList();

    Policy annotations could also be put on fields, to apply when they are passed to/returned from functions, and on parameters, but I haven’t done that yet.

  3. Utiniabitrace
    Posted January 7, 2009 at 10:50 pm | Permalink

    What is bumburbia?

  4. bob
    Posted January 1, 2011 at 5:39 am | Permalink

    for (int i = 0; i < newArgs.length; ++i) { newArgs[i] = args[i + 1]; }

    should be written as

        System.arraycopy(args, 1, newArgs, 0, newArgs.length);
    

Post a Comment

Your email is never shared. Required fields are marked *

*
*