Java – metaprogramming is possible?

January 14, 2011 at 6:37 pm (JRuby, Programming)

Don’t worry, the answer for this question is yeah it’s possible, but there isn’t a natural way to do, the language for itself doesn’t offer options, but we can do through bytecode manipulation, in my opinion this way isn’t exactly a invite to use metaprogramming.
For bytecode manipulation we have two popular options: ASM and javassist, the first is faster but bring complexity in api, the second is more easily to use but is considerably slower than first.

Imagine that we have:

public class SomeClass {
...
}

and:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Marker {
    String value() default "";
}

I would like to add Marker annotation to SomeClass in runtime, so let’s see a helper with two implementations one using ASM and the other Javassist:

ASM

public class AnnotationHelper {

	private static final AsmUtilClassLoader classLoader = new AsmUtilClassLoader();


	public static <T> Class<T> addClassAnnotation(Class<T> clazz, Class<? extends Annotation> annotation) {
		try {
			ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
			new ClassReader(clazz.getName()).accept(new AnnotationClassAdapter(cw, annotation), 0);
			return (Class<T>) classLoader.defineAnnotatedClass(cw, clazz.getName());
		} catch (Exception e) {
			throw new IllegalArgumentException();
		}
	}

	private static class AnnotationClassAdapter extends ClassAdapter {
		private Class<? extends Annotation> annotation;

		private ClassWriter cw;

		public AnnotationClassAdapter(ClassWriter cw, Class<? extends Annotation> annotation) {
			super(cw);
			this.cw = cw;
			this.annotation = annotation;
		}

		@Override
		public void visit(int version, int access, String name,
				String signature, String superName, String[] interfaces) {
			super.visit(version, access, name, signature, superName, interfaces);
			AnnotationVisitor av0;

			av0 = cw.visitAnnotation("L" + annotation.getName().replaceAll("\\.", "/") + ";", true);
			av0.visit("value", "Class");
			av0.visitEnd();
		}
	}

	private static class AsmUtilClassLoader extends ClassLoader {
		private Class<?> defineAnnotatedClass(ClassWriter cw, final String name)  throws ClassNotFoundException {
			byte[] b = cw.toByteArray();
			return defineClass(name, b, 0, b.length);
		}
	}

}

Javassist

public class AnnotationHelper {

	private static final JavassistUtilClassLoader classLoader = new JavassistUtilClassLoader();

	public static <T> Class<T> addClassAnnotation(Class<?> clazz, Class<? extends Annotation> annotation) {
		try {
			CtClass ctClass = ClassPool.getDefault().get(clazz.getName());

			ClassFile classFile = ctClass.getClassFile();
			ConstPool constantPool = classFile.getConstPool();
			AnnotationsAttribute attr = new AnnotationsAttribute(constantPool, AnnotationsAttribute.visibleTag);
			javassist.bytecode.annotation.Annotation a = new javassist.bytecode.annotation.Annotation(annotation.getName(), constantPool);
			attr.setAnnotation(a);
			classFile.addAttribute(attr);
			classFile.setVersionToJava5();
			return (Class<T>) classLoader.defineAnnotatedClass(ctClass);
		} catch (Exception e) {
			throw new IllegalArgumentException();
		}

	}

	private static class JavassistUtilClassLoader extends ClassLoader {

		private Class<?> defineAnnotatedClass(CtClass ctClass)  throws ClassNotFoundException, IOException, CannotCompileException {
			byte[] b = ctClass.toBytecode();
			return defineClass(ctClass.getName(), b, 0, b.length);
		}
	}
}

testing:

Class<SomeClass> c = AnnotationHelper.addClassAnnotation(SomeClass.class, Marker.class);
Annotation a = c.getAnnotation(Marker.class);
System.out.println(a instanceof Marker); //#=> true

On this helper (two implementations) we have to be careful about custom classloader holding the changed class while de default classloader hold the original class, we should have a ClassCastException.

JRuby

It isn’t really a java option, but Jruby has a great java interoperabillity with some facilities to do what we want, in my opinion this is a approach to invite to use metaprogramming:

SomeClass.add_class_annotation({ Marker => {} })
SomeClass.become_java!

Jruby uses ASM for instrumentation, is also a helper like the others, but the essence is different, it’s a core feature, providing to you a easy way to use metaprogramming without the overhead of learn bytecode frameworks.

Advertisements

Permalink Leave a Comment

Tower of Hanoi Performance

May 27, 2009 at 6:16 pm (Programming)

For who doesn’t know, Tower of Hanoi is a interesting puzzle, for more information see http://en.wikipedia.org/wiki/Tower_of_Hanoi, the resolution of this game has exponential complexity, more specifically 2n – 1 where n is the number of disks, this mean that for 3 disks you need 23 – 1 or 7 movements to move the disks to another rod. Now we will see recursive implementation of Tower of Hanoi resolution in three different languages running on JVM. The languages are Java, Clojure (a lisp dialect) and Ruby using JRuby. The JVM is so powerful and there is an active and interesting work to run other languages, with that, allow us to use several implementations JVM based, and too to possibility interoperability in different languages. This post will show performance of each language with the same situation. My goal here isn’t show what language is better or faster, but just make a reflection about performance, is important to analyze that the projects like Jruby and Clojure has much to evolve, they’re relative newer projects.

Test 1 – Clojure: Clojure test performance delight me a lot, we have to considerate that it’s an interpreted language.

Implementation:

;;hanoi.clj
(defn hanoi [n from to via]
       (when (not= n 1)
         (do (hanoi (- n 1) from via to) (recur (- n 1) via to from))))

(time (hanoi 30 "left" "middle" "right"))

Execuction and Performance:

java -server -Xmx1024m -XX:+AggressiveOpts -XX:+UseParallelOldGC -XX:+UseParallelGC -cp /opt/clojure/clojure.jar clojure.lang.Script hanoi.clj

Runtime: 56.714 sec

Test 2 – Jruby: This test disappoints me slightly, this is a great project and ruby is a great language.

Implementation:

#hanoi.rb
def hanoi n,a='left',b='middle',c='right'
    return if n==0
    hanoi (n-1),a,c,b
    hanoi (n-1),c,b,a
end
hanoi 30

Execution and Performance:

jruby -b --server --fast hanoi.rb
Runtime: 188.684 secs

Test 3 – Java: This test is a reference for the other languages. Show us how the other languages JVM based have to grow up.

Implementation

//Hanoi.java
public class Hanoi{
    public void move(int n, String from, String to, String via) {
        if (n != 1) {
            move(n - 1, from, via, to);
            move(1, from, to, via);
            move(n - 1, via, to, from);
        }
    }

    public static void main(String args[]){
        Long start = System.nanoTime();
        new Hanoi().move(30,"left","middle","right");
        System.out.println(System.nanoTime() - start);
    }
}

Execution and Performance:

java -server -Xmx1024m -XX:+AggressiveOpts -XX:+UseParallelOldGC -XX:+UseParallelGC Hanoi
Runtime: 4.83 secs

How you can see, the tests were executed in optimized mode on JVM, and after this analyze, I have opinion that these JVM based languages will be the future.

Permalink 1 Comment