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.

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

Meta-programming on Lisp

April 22, 2009 at 8:39 pm (Lisp)

Meta-programming is a powerfull technique, that allows you to manipulate your code as data, you can ask yourself “How the hell does this work exactly?”, meta-programming looks confusing but after you understand how it works your question can be change to “How to work without this?”.

Lisp is the most famous meta-programming language, it’s so natural working with this technique on Lisp.

Let’s see a simple recursive factorial algorithm, here we could use meta-programming to help us understand how this recursive function works, lisp has a trace macro that shows up step-by-step the code execution:

CL-USER> (defun factorial (x)
          (if (zerop x) 1 (* x (factorial (- x 1)))))
FACTORIAL

CL-USER> (factorial 3)
6

CL-USER> (trace factorial)
;; Tracing function FACTORIAL.
(FACTORIAL)

CL-USER> (factorial 6)
1. Trace: (FACTORIAL '6)
2. Trace: (FACTORIAL '5)
3. Trace: (FACTORIAL '4)
4. Trace: (FACTORIAL '3)
5. Trace: (FACTORIAL '2)
6. Trace: (FACTORIAL '1)
7. Trace: (FACTORIAL '0)
7. Trace: FACTORIAL ==> 1
6. Trace: FACTORIAL ==> 1
5. Trace: FACTORIAL ==> 2
4. Trace: FACTORIAL ==> 6
3. Trace: FACTORIAL ==> 24
2. Trace: FACTORIAL ==> 120
1. Trace: FACTORIAL ==> 720
720

The trace macro changes in execution time the factorial code to print the function steps, now we can have an idea on how to apply meta-programming in real world examples.

Now let’s implement a macro to simulate a for statement:

(defmacro for (var start stop &body body)
  (let ((gstop (gensym)))
    `(do ((,var ,start (1+ ,var))
          (,gstop ,stop))
         ((> ,var ,gstop))
       ,@body)))

Lisp has macroexpand-1 to show the code generated for a macro, therefore:

(macroexpand-1 '(for i 10 20 (print i)))

Results:

(DO ((I 10 (1+ I)) (#:G4588 20))
      ((> I #:G4588))  (PRINT I))

You can see how macros are powerful, allowing you to build sctrutctures easily, and extend the language to satisfy your need.

Wrapping up, meta-programming puts your code to work for you, think about it.

Permalink 1 Comment

First post

April 15, 2009 at 2:12 am (Uncategorized)

Blog officially opened………..

Permalink 1 Comment

Follow

Get every new post delivered to your Inbox.