Programme mit der Compiler API übersetzen
Zum Übersetzen von Java-Programmen definiert Java 6 eine standardisierte API
über das Paket javax.tools. Vor Java 6 mussten Entwickler entweder direkt den
Java-Compiler von Sun ansprechen oder über eine externe Bibliothek wie den
Apache Commons Java Compiler Interface (JCI) unter
http://tutego.com/go/jci einen Compiler, etwa den von Eclipse, ansprechen.
Java Compiler API
Wir wollen mit dem Java Compiler API aus Java 6 ein Programm entwickeln, das Java-Quellcode auf die Festplatte schreibt, diesen dann übersetzt und anschließend lädt. Zum ersten Teil:
Writer p = new FileWriter( "c:/A.java" );
p.write( "class A { static { System.out.println(\"Java Compiler API\"); } }" );
p.close();
Der FileWriter schreibt ein einfaches Java-Programm auf das Laufwerk
C:/. Der
Compiler kann das Programm nun übersetzen, wobei er natürlich auch das Programm
als Eingabestrom empfangen kann.
JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = tool.getStandardFileManager( null, null, null );
List<File> fileList = Arrays.asList( new File("c:/A.java") );
Iterable<? extends JavaFileObject> units;
units = manager.getJavaFileObjectsFromFiles( fileList );
CompilationTask task = tool.getTask( null, manager, null, null, null, units );
task.call();
manager.close();
Der ToolProvider gibt mit der statischen Funktion ein Objekt vom Typ
JavaCompiler zurück, das den Compiler repräsentiert. Das Objekt steuert die
Übersetzung und die Fehlermeldungen. Mit dem JavaCompiler sind ein optionaler
DiagnosticListener verbunden und ein StandardJavaFileManager, den getStandardFileManager()
liefert. Er bestimmt mit getJavaFileObjectsFromFiles() oder
getJavaFileObjectsFromStrings() die Eingabedateien als Liste von
JavaFileObject-Objekten. Mit dem File-Manager und der Liste der
JavaFileObject-Objekte liefert das JavaCompilerTool-Objekt anschließend mit
getTask() ein JavaCompilerTool.CompilationTask-Objekt, das call() anbietet, um
die Übersetzung zu starten.
Im letzten Schritt kann ein eigener Klassenlader die Klasse laden, und es folgt eine Ausgabe auf dem Bildschirm:
URLClassLoader classLoader = new URLClassLoader(
new URL[] { new File( "c:/" ).toURI().toURL() } );
Class.forName( "A", true, classLoader ); // Java Compiler API
Im String angegebene Klasse übersetzen
Zum Übersetzen von Strings muss das Programm nicht erst eine temporäre Datei
schreiben. Zwar sieht die Compiler-API nicht direkt eine einfache Funktion zum
Übersetzten von Klassen aus Strings vor, doch schwierig ist es auch nicht. Der
erste Schritt ist die Entwicklung einer eigenen JavaFileObject-Klasse.
class JavaSourceFromString extends SimpleJavaFileObject
{
private final String code;
public JavaSourceFromString( String name, String code )
{
super( URI.create( "string:///" + name.replace( '.', '/' ) + Kind.SOURCE.extension ), Kind.SOURCE );
this.code = code;
}
@Override
public CharSequence getCharContent( boolean ignoreEncodingErrors )
{
return code;
}
}
Der Konstruktor erwartet einen Namen der Klasse und den String mit der Klassendefinition.
Jetzt können wir fast so vorgehen wie im letzten Beispiel. Erst bauen wir das
JavaFileObject auf, bauen für getTask() ein Iterable<? extends JavaFileObject>
mit diesem einen Objekt und rufen call() auf.
String src = "class B { static { System.out.println(\"Aus der Ursuppe\"); } }";
JavaFileObject javaFile = new JavaSourceFromString( "B", src );
units = Arrays.asList( javaFile );
task = tool.getTask( null, manager, null, null, null, units );
task.call();
Das übersetzt die Klasse und ermöglicht später das Laden:
Class.forName( "B" ); // Aus der Ursuppe
Kompletter Quellcode für das Beispiel
package com.tutego.insel.tools;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.List;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
public class CompileDemo
{
public static void main( String[] args ) throws Exception
{
Writer p = new FileWriter( "c:/A.java" );
p.write( "class A { static { System.out.println(\"Java Compiler API\"); } }" );
p.close();
//
JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = tool.getStandardFileManager( null, null, null );
List<File> fileList = Arrays.asList( new File("c:/A.java") );
Iterable<? extends JavaFileObject> units;
units = manager.getJavaFileObjectsFromFiles( fileList );
CompilationTask task = tool.getTask( null, manager, null, null, null, units );
task.call();
String src = "class B { static { System.out.println(\"Aus der Ursuppe\"); } }";
JavaFileObject javaFile = new JavaSourceFromString( "B", src );
units = Arrays.asList( javaFile );
task = tool.getTask( null, manager, null, null, null, units );
task.call();
manager.close();
//
URLClassLoader classLoader = new URLClassLoader(
new URL[] { new File( "c:/" ).toURI().toURL() } );
Class.forName( "A", true, classLoader ); // Java Compiler API
Class.forName( "B" ); // Aus der Ursuppe
}
}
class JavaSourceFromString extends SimpleJavaFileObject
{
private final String code;
public JavaSourceFromString( String name, String code )
{
super( URI.create( "string:///" + name.replace( '.', '/' )
+ Kind.SOURCE.extension ), Kind.SOURCE );
this.code = code;
}
@Override
public CharSequence getCharContent( boolean ignoreEncodingErrors )
{
return code;
}
}