-- an Embedded JavaTM Compiler
 

Use Janino as...

Well, Janino is sort of a "swiss army knife" and has many applications. These are available through nice wrapper classes that encapsulate the application as conveniently (and still as flexible) as possible.

The following sections help you decide which Janino application to use in your specific environment.

Janino as an Expression Evaluator

Say you build an e-commerce system, which computes the shipping cost for the items that the user put into his/her shopping cart. Because you don't know the merchant's shipping cost model at implementation time, you could implement a set of shipping cost models that come to mind (flat charge, by weight, by number of items, ...) and select one of those at run-time.

In practice, you will most certainly find that the shipping cost models you implemented will rarely match what the merchant wants, so you must add custom models, which are merchant-specific. If the merchant's model changes later, you must change your code, re-compile and re-distribute your software.

Because this is so unflexible, the shipping cost expression should be specified at run-time, not at compile-time. This implies that the expression must be scanned, parsed and evaluated at run-time, which is why you need an expression evaluator.

A simple expression evaluator would parse an expression and create a "syntax tree". The expression "a + b * c", for example, would compile into a "Sum" object who's first operand is parameter "a" and who's second operand is a "Product" object who's operands are parameters "b" and "c". Such a syntax tree can evaluated relatively quickly. However, the run-time performance is about a factor of 100 worse than that of "native" Java code executed directly by the JVM. This limits the use of such an expression evaluator to simple applications.

Also, you may want not only do simple arithmetics like "a + b * c % d", but take the concept further and have a real "scripting" language which adds flexibility to your application. Since you know the Java programming language already, you may want to have a syntax that is similar to that of the Java programming language.

All these considerations lead to compilation of Java code at run-time, like some engines (e.g. JSP engines) already do. However, compiling Java programs with SUN's JDK is a relatively resource-intensive process (disk access, CPU time, ...). This is where Janino comes into play... a leight-weight, "embedded" Java compiler that compiles simple programs in memory into Java bytecode which executes within the JVM of the running program. (See the source code of ExpressionEvaluator for an example.)

OK, now you are curious... this is how you use the ExpressionEvaluator:

      // Compile the expression once; relatively slow.
      ExpressionEvaluator ee = new ExpressionEvaluator(
          "c > d ? c : d",                     // expression
          int.class,                           // expressionType
          new String[] { "c", "d" },           // parameterNames
          new Class[] { int.class, int.class } // parameterTypes
      );

      // Evaluate it with varying parameter values; very fast.
      Integer res = (Integer) ee.evaluate(
          new Object[] {          // parameterValues
              new Integer(10),
              new Integer(11),
          }
      );
      System.out.println("res = " + res);

Notice: If you pass a string literal as the expression, be sure to escape all JavaTM special characters, especially backslashes.

Compilation of the expression takes 670 microseconds on my machine (2 GHz P4), evaluation 0.35 microseconds (approx. 2000 times faster than compilation).

Janino as a Script Evaluator

Analogously to the expression evaluator, a script evaluator exists that compiles and processes a JavaTM "block", i.e. the body of a method. If a return value other than "void" is defined, then the block must return a value of that type. Example:

    System.out.println("Hello world");
    return true;

Janino as a Class Body Evaluator

Analogously to the expression evaluator and the script evaluator, a class body evaluator exists that compiles and processes the body of a JavaTM class, i.e. a series of method an variable declarations. If you define a contract that the class body should define a method named "main()", then your script will look almost like a "C" program:

    import java.util.*;

    public static void main(String[] args) {
        Vector v = new Vector();
        for (int i = 0; i < args.length; ++i) {
            v.add(args[i]);
        }
        System.out.println("Command line args converted into vector!");
    }

Janino as a Simple Compiler

The SimpleCompiler compiles a single "compilation unit" (a.k.a. ".java" file). Opposed to to normal JavaTM compilation, that compilation unit may define more than one public class. Example:

    package my.pkg;

    import java.util.*;

    public class A {
        public static void main(String[] args) {
            B b = new B();
            b.meth1();
        }
    }

    public class B {
        void meth1() {
            System.out.println("Hello there.");
        }
    }

Janino as a Source Code ClassLoader

The JavaSourceClassLoader extends JavaTM's java.lang.ClassLoader class with the ability to load classes directly from source code.

To be precise, if a class is loaded through this ClassLoader, it searches for a matching ".java" file in any of the directories specified by a given "source path", reads, scans, parses and compiles it and defines the resulting classes in the JVM. As necessary, more classes are loaded through the parent class loader and/or through the source path. No intermediate files are created in the file system.

Example:

    /* srcdir/pkg1/A.java */
    package pkg1;
    import pkg2.*;
    public class A extends B {
    }

    /* srcdir/pkg2/B.java */
    package pkg2;
    public class B implements Runnable {
        public void run() {
            System.out.println("HELLO");
        }
    }

    // Sample code that reads, scans, parses, compiles and loads "A.java" and "B.java",
    // then instantiates an object of class "A" and invokes its "run()" method.
    ClassLoader cl = new JavaSourceClassLoader(
        this.getClass().getClassLoader(),  // parentClassLoader
        new File[] { new File("srcdir") }, // optionalSourcePath
        (String) null,                     // optionalCharacterEncoding
        DebuggingInformation.NONE          // debuggingInformation
    );

    // Load class A from "srcdir/pkg1/A.java", and also its superclass B from
	// "srcdir/pkg2/B.java":
    Object o = cl.loadClass("pkg1.A").newInstance();

    // Class "B" implements "Runnable", so we can cast "o" to "Runnable".
    ((Runnable) o).run(); // Prints "HELLO" to "System.out".

If the JavaTM source is not available in files, but from some other storage (database, main memory, ...), you may specify a custom ResourceFinder instead of the directory-based source path.

If you have many source files and you want to reduce the compilation time, you may want to use the CachingJavaSourceClassLoader, which uses a cache provided by the application to store class files for repeated use.

A BASH shell script named "bin/janino" is provided that wraps the JavaSourceClassLoader in a JAVAC-like command line interface:

    $ cat my/pkg/A.java
    package my.pkg;

    import java.util.*;

    public class A {
        public static void main(String[] args) {
            B b = new B();
            b.meth1();
        }
    }

    class B {
        void meth1() {
            System.out.println("Hello there.");
        }
    }
    $ type janino
    /usr/local/bin/janino
    $ janino my.pkg.A
    Hello there.
    $

Janino as a Command-Line JavaTM Compiler

The Compiler class mimics the behavior of SUN's javac tool. It compiles a set of "compilation units" (i.e. JavaTM source files) into a set of class files.

Using the "-warn" option, Janino spits out some probably very interesting warnings which may help you to "clean up" the source code.

The BASH script "bin/janinoc" implements a drop-in replacement for SUN's JAVAC utility:

    $ janinoc -sourcepath src -d classes src/com/acme/MyClass.java
    $ janinoc -help
    A drop-in replacement for the JAVAC compiler, see the documentation for JAVAC
    Usage:
      janinoc [ <option> ] ... <class-name> [ <argument> ] ...
    Options:
      -sourcepath <dir-list>    Where to look for source files
      -classpath <dir-list>     Where to look for class files
      -cp <dir-list>            Synonym for "-classpath"
      -extdirs <dir-list>       Where to look for extension class files
      -bootclasspath <dir-list> Where to look for boot class files
      -encoding <encoding>      Encoding of source files, default is platform-dependent
      -verbose                  Report about opening, parsing, compiling of files
      -g                        Generate all debugging info
      -g:none                   Generate no debugging info
      -g:{lines,vars,source}    Generate only some debugging info
      -warn:<pattern-list>      Issue certain warnings, examples:
        -warn:*                 Enables all warnings
        -warn:IASF              Only warn against implicit access to static fields
        -warn:*-IASF            Enables all warnings, except those against implicit
                                access to static fields
        -warn:*-IA*+IASF        Enables all warnings, except those against implicit
                                accesses, but do warn against implicit access to
                                static fields
      -rebuild                  Compile all source files, even if the class files
                                seems up-to-date
      -n                        Print subcommands to STDOUT instead of running them
      (any valid command-line optipon for the JAVA tool, see "java -help")
    $

Janino as an ANT Compiler

You can plug JANINO into the utility through the AntCompilerAdapter class. Just make sure that janino.jar is on the class path, then run ANT with the following command-line option:

    -Dbuild.compiler=org.codehaus.janino.AntCompilerAdapter

Janino as a TOMCAT Compiler

If you want to use JANINO with TOMCAT, just copy the "janino.jar" file into TOMCAT's "common/lib" directory, and add the follwing init parameter section to the JSP servlet definition in TOMCAT's "conf/web.xml" file:

    <init-param>
        <param-name>compiler</param-name>
        <param-value>org.codehaus.janino.AntCompilerAdapter</param-value>
    </init-param>

Janino as a Code Analyser

Apart from compiling JavaTM code, JANINO can be used for static code analysis: Based on the AST ("abstract syntax tree") produced by the parser, the Traverser walks through all nodes of the AST, and derived classes can do all kinds of analyses on them, e.g. count declarations:

    $ java org.codehaus.janino.samples.DeclarationCounter DeclarationCounter.java
    Class declarations:     1
    Interface declarations: 0
    Fields:                 4
    Local variables:        4
    $

This is the basis for all these neat code metrics and style checking.

Janino as a Code Manipulator

If, e.g., you want to read a JavaTM compilation unit into memory, manipulate it, and then write it back to a file for compilation, then all you have to do is:

    // Read the compilation unit from Reader "r" into memory.
    Java.CompilationUnit cu = new Parser(new Scanner(fileName, r)).parseCompilationUnit();

    // Manipulate the AST in memory.
    // ...

    // Convert the AST back into text.
    UnparseVisitor.unparse(cu, new OutputStreamWriter(System.out));

The UnparseVisitor class demostrates how to do this.