See: Description
| Package | Description |
|---|---|
| org.codehaus.commons.compiler |
This package declares interfaces for the implementation of an
IExpressionEvaluator, an IScriptEvaluator, an
IClassBodyEvaluator and an ISimpleCompiler. |
| org.codehaus.commons.compiler.jdk |
An implementation of the
org.codehaus.commons.compiler API that uses the "JAVAC" Java compiler that is
part of the "Java Development Kit" (JDK). |
| org.codehaus.commons.compiler.samples |
Sample applications for the Janino JavaTM compiler.
|
| org.codehaus.commons.io |
I/O utilities.
|
| org.codehaus.commons.nullanalysis |
Annotations for ECLIPSE's "null analysis" feature.
|
| org.codehaus.janino |
The core of the Janino JavaTM compiler.
|
| org.codehaus.janino.samples |
Sample applications for the Janino JavaTM compiler.
|
| org.codehaus.janino.tools |
Auxiliary command line tools related to JANINO.
|
| org.codehaus.janino.util |
Application-independent helper classes.
|
| org.codehaus.janino.util.iterator |
Some generic
Iterator-related helper classes. |
| org.codehaus.janino.util.resource |
Classes related to loading "resources" (
ResourceFinder) and creating
resources (ResourceCreator). |
JANINO is a non-trivial piece of software, so I'll depict its data model in several steps in order to explain the various aspects.
org.codehaus.janino.CompilerThis compiler reads .java files, compiles them, and creates .class files.
+-----------------+ +-----------------+
| |\ | |\
| .java files +-+ | .class files +-+
| on command line | | in dest dir |
| | | |
+-------------------+ +-------------------+
| ^
parsed by |
o.c.j.Parser |
into written to
| |
v |
+-------------------------------------+ +----------------------------+
| Java.CompilationUnit | | o.c.j.util.ClassFile |
+-------------------------------------+ +----------------------------+
| Java.PackageMemberTypeDeclaration[] | | ClassFile.AttributeInfo[] |
| Java.Annotation[] | | ClassFile.FieldInfo[] |
| Java.TypeParameter[] | | ClassFile.AttributeInfo[] |
| Java.FieldDeclaration[] | | ClassFile.MethodInfo[] |
| Java.Annotation[] | compiled by | ClassFile.AttributeInfo[] |
| Java.MemberTypeDeclaration[] |-- o.c.j.UnitCompiler -->| |
| ... | into | |
| Java.ConstructorDeclarator[] | | |
| ... | | |
| Java.MethodDeclarator[] | | |
| Java.Annotation[] | | |
| Java.TypeParameter[] | | |
| Java.BlockStatements[] | | |
+-------------------------------------+ +----------------------------+
As you know, a ".java" source file can contain multiple type declarations (exactly one of which must be PUBLIC), so that's why the Java Language Specification (JLS) calls a ".java" file a "compilation unit".
The org.codehaus.janino.Parser (abbreviated as "o.c.j.Parser" in the
figure above) parses the compilation unit, understands the Java grammar, and creates a tree of objects that
exactly reflects the contents of the compilation unit (except some irrelevant aspects like
(non-JAVADOC) comments and the ordering of access flags). The classes names of the objects of the syntax tree are
all "org.codehaus.janino.Java.*".
Like the Java.CompilationUnit structure is a one-to-one representation of the parsed
compilation unit, the org.codehaus.janino.util.ClassFile.MethodInfo
structure on the bottom right of the figure is a one-to-one representation of a class file. (If you're not familiar
with the Java class file format, read the
respective section in the "Java Virtual Machine Specification".)
The UnitCompiler's job is to translate the compilation unit into class files. For
example, a Java.MethodDeclarator is transformed into a ClassFile.MethodInfo, the Java.BlockStatements of the method
declarator into a ClassFile.CodeAttribute of the ClassFile.MethodInfo object, and so forth.
After processing all input files this way, we have a set of class files that are ready to be loaded into a JVM.
Job done? No. This compiler would be very limited, for two reasons:
java.lang.Object and
java.lang.String). Not good.
Effectively, this compiler is not of much use. It could only compile fields and methods that use primitive
types (boolean, int, ...). So we must do better!
First we address the problem of arbitrary references within one compilation unit. The trick is, while compiling one type declaration, to look at the other type declarations in the same compilation unit (whether they are already compiled or not). But because the "other" type declaration is not yet compiled, its superclass name, implemented interfaces names, fields types, method return types, method parameter types asf. are not yet "resolved". E.g. for
package my.package;
import com.acme.Person;
class Class1 {
public void
myMethod(String s, Person p, Car c) {
// ...
}
}
class Car { ... }
, the type names "String", "Person" and "Car" have not yet been resolved into "java.lang.String", "com.acme.Person" and "my.package.Car".
Therefore, a third representation of a class/interface must be introduced, which (for JANINO) is the
IClass hierarchy:
+-----------------+ +-----------------+
| |\ | |\
| .java files +-+ | .class files +-+
| on command line | | in dest dir |
| | | |
+-------------------+ +-------------------+
| ^
parsed by |
o.c.j.Parser |
into written to
| |
v |
+----------------------+ +----------------------+
| | compiled by | |
| Java.CompilationUnit |-- o.c.j.UnitCompiler -->| o.c.j.util.ClassFile |
| | into | | |
+----------------------+ | +----------------------+
uses
|
v
+----------------------------+
| o.c.j.IClass |
+----------------------------+
| IAnnotation[] |
| IField[] |
| ... |
| IConstructor[] |
| ... |
| IMethod[] |
| IAnnotation[] |
| IClass returnType |
| IClass[] parameterTypes |
| IClass[] thrownExceptions |
| ... |
| ... |
+----------------------------+
An IClass represents the "outside" of a usable class or interface, which JANINO needs when that class or
interface is "used".
Now where do we get the IClasses from? A complete implementation requires three different sources:
+-----------------+ +-----------------+ +-----------------+ +-----------------+
| |\ | |\ | |\ | |\
| .java files +-+ | .java files +-+ | .class files +-+ | .class files +-+
| on command line | | on sourcepath | | in dest dir | | on classpath |
| | | | | | | |
+-------------------+ +-------------------+ +-------------------+ +-------------------+
| ^ ^ |
parsed by | | |
o.c.j.Parser <----------------------| | parsed into
into | written to |
| finds | |
v | | v
+----------------------+ | +----------------------+ +----------------------+
| | compiled by | | | | |
| Java.CompilationUnit |-- o.c.j.UnitCompiler -------->| o.c.j.util.ClassFile | | o.c.j.util.ClassFile |
| | into | | | | |
+----------------------+ | +----------------------+ +----------------------+
| uses |
wrapped by | wrapped by
o.c.j.UnitCompiler.resolve() v o.c.j.ResourceFinderIClassLoader
as a +----------------+ +-----------------------+ as a
| | | | | |
+------------------>| o.c.j.IClass |<-implements-| o.c.j.ClassFileIClass |<------+
| | | |
+----------------+ +-----------------------+
Car" (declared in the same compilation unit), the UnitCompiler uses its "UnitCompiler.resolve(org.codehaus.janino.Java.TypeDeclaration)" method (bottom left), which wraps a parsed type declaration as an
IClass.
java.lang.String" (found on the compilation classpath), the UnitCompiler uses an
animal called the "org.codehaus.janino.ResourceFinderIClassLoader", which searches the classpath for a resource named "java/lang/String.class", loads it via "ClassFile.ClassFile(java.io.InputStream)"
and wraps it as an IClass (bottom right).
com.acme.Person" (declared in a different compilation unit), the UnitCompiler searches and finds a resource "com/acme/Person.java" on the sourcepath, parses it (center)
and uses "UnitCompiler.resolve(org.codehaus.janino.Java.TypeDeclaration)" to wrap the type declaration "Person" as an
IClass.
And Bob's your uncle! That is everything that org.codehaus.janino.Compiler
does.
Typically, to compile a small set of compilation units, many other required classes have to be loaded and parsed via the compilation classpath. This costs a considerable amount of time and memory.
For "embedded" applications, i.e. when you want to compile and load classes in the same running JVM, it
is much more efficient to use the loaded required classes, instead of parsing class files. Basically that
is what the org.codehaus.janino.SimpleCompiler does:
+-----------------+ +-------------------------------------+
| |\ | |
| .java file +-+ | The running JVM's |
| or code snippet | | java.lang.ClassLoader |
| | | |
+-------------------+ +-------------------------------------+
| ^ |
parsed by | finds via
o.c.j.Parser loaded via ClassLoader.loadClass()
into java.lang.ClassLoader.defineClass() the
| into |
v | v
+----------------------+ +----------------------+ +----------------------+
| | compiled by | | | |
| Java.CompilationUnit |-- o.c.j.UnitCompiler -->| o.c.j.util.ClassFile | | java.lang.Class |
| | into | | | | |
+----------------------+ | +----------------------+ +----------------------+
| uses |
wrapped by | wrapped by
o.c.j.UnitCompiler.resolve() v o.c.j.ResourceFinderIClassLoader
as a +----------------+ +------------------------+ as a
| | | | | |
+-------------->| o.c.j.IClass |<-implements-| o.c.j.ReflectionIClass |<------+
| | | |
+----------------+ +------------------------+
The
org.codehaus.janino.ClassBodyEvaluator,
org.codehaus.janino.ScriptEvaluator and the
org.codehaus.janino.ExpressionEvaluator
are merely variants of the SimpleCompiler that call, instead of
Parser.parseCompilationUnit(), the
Parser.parseClassBody(org.codehaus.janino.Java.AbstractClassDeclaration) method, resp.
Parser.parseMethodBody(), resp.
Parser.parseExpression().