JRuby: Reading Java Annotations
I have shown examples of how JRuby could invoke Java classes, in particular SWT components. Here is now another example, this time using JRuby to read annotations in Java classes.
Here is the situation: when using FitNesse, developers sometimes have to develop what is called fixtures, that is, Java classes that can be used (usually by a business analyst) to write tests in the FitNesse wiki. Fixtures then perform the actual testing and return the result so that it can be displayed on the wiki. They can be very straightforward, or they can interact with web pages (via Selenium, for instance) or even call web services. As they are to be used by non-developers, they have to be properly documented. One way to do this is simply by publishing the JavaDoc – but that would kind of ruin my JRuby example1!
So let’s use annotations to document our fixtures. Here is an example of an annotation that could be used:
package com.weblogism.jruby; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface FixtureHint { String usage(); String description(); }
The RetentionPolicy
indicates that this annotation will be available at runtime.
Fixtures can then be documented as follows2:
package com.weblogism.jruby; public class TheFixture { @FixtureHint(usage="| click the | _button_id_ | button |", description="clicks _button_id_") public boolean clickTheButton() { System.out.println("Click"); return true; } }
These classes are nicely packaged in a jar called test.jar
. So how to use JRuby to find the annotations? Extremely simple:
require 'java' require 'lib/test.jar' include_class 'com.weblogism.jruby.TheFixture' include_class 'com.weblogism.jruby.FixtureHint'
You first import all the Java stuff, such as your classes. Don’t forget that these include_class
can be called dynamically, and therefore you could potentially search for all the relevant classes in the jar, and then import them all. Here, the jar is explicitly imported (it is located in the lib
directory of the current working dir), but another way to make it “visible” to the script is add it to the $CLASSPATH
environment var.
annotations = Hash.new TheFixture.java_class.declared_instance_methods.each do |m| if m.annotation_present?(FixtureHint.java_class) annotation = m.annotation(FixtureHint.java_class) annotations[m.name] = annotation end end annotations.values.each do |a| puts "#{a.usage()}\t#{a.description()}" end
And that’s as simple as that: the Java methods isAnnotationPresent
and getAnnotation
become annotation_present?
and annotation
(à la ruby), and once they have been found, they can be manipulated like ruby objects.
JRuby version:
sebastien@greystones:~/workspace/sandbox$ jruby -v jruby 1.6.0.dev (ruby 1.8.7 patchlevel 249) (2010-08-10 f740f78) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_20) [amd64-java]
1 The example might be a bit convoluted, but it illustrates the use of annotations through a real-life requirement.
2 Fixtures would usually extend a fixture class, e.g. DoFixture
, ColumnFixture
, etc. but here it isn’t to keep things simple.