So I've recently decided to create an R interface to one of my Java projects to streamline analysis and ultimately, to make it more usable for other people. As pretty much the only option for running Java code in R, the rJava package makes things pretty simple. You just initialize the jvm with your required jar files and then use .jnew and .jcall to construct objects and run methods. I found the rJava
vignettes quite helpful and the
package documentation has more detail when you need it.
However, the API for my Java code is in a bit of flux right now and for at least the near future., so I didn't want to put in too much effort that would be lost as soon as the Java code changes. But I also wanted to start to get a sense as to how things would work going between R and Java because that could influence some of my design decisions (e.g. type inference, casting, etc.). In particular, I've been having a hard time deciding how much I should use generics. They're handy for the "type-checking" but a pain in the butt in terms of verbosity.
Anyway, rJava seemed nice and straightforward, but I know my java code would change, so I got a crazy idea in my head. What if I could use Java's user-defined
annotations to markup the constructors and methods I wanted exposed in R and then use the
apt annotation processing tool to convert those annotations into R code? Well, it turns out, you can, and it only distracted me from my research for about an afternoon.
The actual definition of your annotation is quite simple:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR })
public @interface AnnotateR {
}
And using your new annotation is just as easy:
public class HelloJavaWorld {
String message = "Hello Java World!";
public HelloJavaWorld() {
}
/**
* Create a simple hello world message from Java using rJava
*
* @param message
* the message to be sent
*/
@AnnotateR
public HelloJavaWorld(String message) {
if (message != null) {
this.message = message;
}
}
@AnnotateR
public String sayHello() {
String result = new String("Hello Java World!");
return result;
}
@AnnotateR
public void sayVoid() {}
@AnnotateR
public int[] sayArray() {
return new int[] { 1, 2, 3 };
}
}
Now in order to do anything useful with those nice new annotations, you have to define a Factory class:
public class AnnotateRJavaFactory implements AnnotationProcessorFactory {
// Process only the R annotations
private static final Collection<String> supportedAnnotations = unmodifiableCollection(Arrays
.asList("net.sourceforge.jannotater.AnnotateR"));
// No supported options
private static final Collection<String> supportedOptions = emptySet();
public Collection<String> supportedAnnotationTypes() {
return supportedAnnotations;
}
public Collection<String> supportedOptions() {
return supportedOptions;
}
public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env) {
return new AnnotateRProcessor(atds, env);
}
}
This basically just generates objects of the class that does the work:
public class AnnotateRProcessor extends SimpleDeclarationVisitor implements AnnotationProcessor {
private final AnnotationProcessorEnvironment mEnv;
private final Set<AnnotationTypeDeclaration> mAnnotationDeclarations;
.......
.....
AnnotateRProcessor(Set<AnnotationTypeDeclaration> annotationDeclarations, AnnotationProcessorEnvironment env) {
this.mAnnotationDeclarations = annotationDeclarations;
this.mEnv = env;
}
public void process() {
for (AnnotationTypeDeclaration annotationType : this.mAnnotationDeclarations) {
for (Declaration typeDecl : mEnv.getDeclarationsAnnotatedWith(annotationType)) {
typeDecl.accept(getDeclarationScanner(this, NO_OP));
}
}
}
@Override
public void visitMethodDeclaration(MethodDeclaration d) {
//Process a method
}
}
Then it's just a matter of running it all (using the -factory option forces apt to use your AnnotationProcessor):
apt -cp ./jannotater.jar:$JAVA_HOME/lib/tools.jar -factory net.sourceforge.jannotater.AnnotateRJavaFactory -nocompile -s ${packagename}_tmp $srcfiles
In my case, it also took some reading up on the
structure of R packages and then I decided I might as well insert the javadoc comments into the R interface and use
inlinedocs to generate the R documentation that are used to generate the help and manuals.
Anyway, I got it all working and threw it up onto
sourceforge. For not I just focused on the basic functionality of exposing constructors and methods, but it wouldn't be hard to extend it to expose attributes, too. Also worth mentioning is that I'm doing absolutely nothing to prevent name collisions, so watch out for those. Despite the limitations, though, this makes it absolutely easy-peasy to expose Java functionality to R. Hopefully this is also of value to people who are not me.