Wednesday, November 28, 2012

Create new Java Class files at runtime

When we write programs usual method is to write all the code compile it and run. But when we try to complex things some times we may want to generate a new class and use it at runtime. As an example I wanted to create a class which parse a xml message and create a new Java object from that data. This is used in web server. So that result object varies according to the web service which is deployed. The solution was to inspect the service at deployment time and create a new parser class according to that in the service deployment time. It has to be noted that this parser had to be generated as a .class file and dynamically added to the classpath. So the result was to use a bytecode manupulation library. There are few bytecode manuplulation libraries available as ASM and JavaAssist.
I used JavaAssist for this. The reason for using JavaAssist is it is simple to create a new class from the scratch using JavaAssit. When we create new class files using JavaAssit we have to create all the methods in the new class as Strings and the pass them to Java Assist. As a example if we have to add amethod called sayHellow() to new class we can create it as a String like this.
String s="public void sayHello(){ System.out.println("Hello"); }

/**
     * @param c   Any class which can be used to get the class loader                          (c.getClassLoarder()) can be passed using object.getClass() methood
     * @param name
     *            Name of the to be generated class
     * @param methods
     *            Methods to be created as Strings
     * @param interfaces
     *            interfaces to be implemented as Strings
     * @param directory
     *            the directory generated class files has to be written
     */
    public static void createClass(Class<?> c, String name, List<String> methods,
            List<String> interfaces, String directory) {
        String temp = null;
        try {
            ClassPool pool = ClassPool.getDefault();
            pool.insertClassPath(new ClassClassPath(c));
            CtClass cc = pool.makeClass(name);
            if (interfaces != null) {
                for (String s : interfaces) {
                    CtClass anInterface = pool.get(s);
                    cc.addInterface(anInterface);
                }
            }
            for (String s : methods) {
                temp = s;
                CtMethod m = CtNewMethod.make(s, cc);
                cc.addMethod(m);
            }
            cc.writeFile(directory);
        } catch (Exception e) {
            // TODO throw
            System.out.println(temp);
            e.printStackTrace();
        }
    }

To add this class to the classpath following method can be used.

//Here directory is the path which class files were written

 public static Class loadClass(String className,String directory, ClassLoader loader) throws Exception {
        File f = new File(directory);
        java.net.URL[] urls = new java.net.URL[] { f.toURI().toURL() };
        ClassLoader cl = new URLClassLoader(urls, loader);
        Class cls = cl.loadClass(className);
        return cls;
    }

Note - JavaAssist does not support generics. If we use genarics java assist throws an exception. We have to use fully qualified names for types. As an example we have to use java.util.List for List type.