Der Zugriff zum Java-Compiler ist über die Java-Compiler-API standardisiert, jedoch sind alle Interna, wie die tatsächliche Repräsentation des Programmcodes verborgen. Die Compiler-API abstrahiert alles über Schnittstellen, und so kommen Entwickler nur mit JavaCompiler, StandardJavaFileManager und CompilationTask in Kontakt – alles Schnittstellen aus dem Paket javax.tools. Um etwas tiefer einzusteigen, lässt sich zum einem Trick greifen: Klassen implementieren Schnittstellen und wenn ein Programm den Schnittstellentyp auf den konkreten Klassentyp anpasst, dann stehen in der Regel mehr Methoden zur Verfügung. So lässt sich der CompilationTask auf eine com.sun.tools.javac.api.JavacTaskImpl casten und dann steht eine parse()-Methode für Verfügung. Die parse()-Methode liefert als Rückgabe eine Aufzählung von CompilationUnitTree. Um diesen Baum nun abzulaufen, lässt sich das Besuchermuster einsetzen. CompilationUnitTree bietet eine accept(…)-Methode; der übergeben wir einen TreeScanner. Die accept(…)-Methode ruft dann beim Ablaufen jedes Knotens unseren Besucher auf.
package com.tutego.tools.javac; import java.io.*; import java.net.*; import javax.tools.*; import javax.tools.JavaCompiler.CompilationTask; import com.sun.source.tree.*; import com.sun.source.util.TreeScanner; import com.sun.tools.javac.api.JavacTaskImpl; public class PrintAllMethodNames { final static TreeScanner<?, ?> methodPrintingTreeVisitor = new TreeScanner<Void, Void>() { @Override public Void visitCompilationUnit( CompilationUnitTree unit, Void arg ) { System.out.println( "Paket: " + unit.getPackageName() ); return super.visitCompilationUnit( unit, arg ); }; @Override public Void visitClass( ClassTree classTree, Void arg ) { System.out.println( "Klasse: " + classTree.getSimpleName() ); return super.visitClass( classTree, arg ); } @Override public Void visitMethod( MethodTree methodTree, Void arg ) { System.out.println( "Methode: " + methodTree.getName() ); return super.visitMethod( methodTree, arg ); } }; public static void main( String[] args ) throws IOException, URISyntaxException { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); URI filename = PrintAllMethodNames.class.getResource( "PrintAllMethodNames.java" ).toURI(); Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjects( new File( filename ) ); CompilationTask task = compiler.getTask( null, null, null, null, null, fileObjects ); JavacTaskImpl javacTask = (JavacTaskImpl) task; for ( CompilationUnitTree tree : javacTask.parse() ) tree.accept( methodPrintingTreeVisitor, null ); } }
Ein TreeScanner hat viele Methoden, wir interessieren uns nur für den Start einer Compilationseinheit für den Paketnamen, für alle Klassen und Methoden. Wir könnten uns aber auch über alle Annotationen oder do-while-Schleifen informieren lassen. Die Ausgabe ist:
Paket: com.tutego.tools.javac
Klasse: PrintAllMethodNames
Klasse:
Methode: visitCompilationUnit
Methode: visitClass
Methode: visitMethod
Methode: main
Die zweite Angabe für den Klassennamen ist leer, da die anonyme Klasse eben keinen Namen hat.
Hi wirklich guter Blog und immer wieder interessante Einträge,
aber bitte nutze doch endlich syntax highlighting,
ist doch kein gimmick mehr, wenn man das nutzt, sonst liest sich code echt schlecht, oder schreibst du noch mit notepad deine Java-Klassen?
😉
Trotzdem weiter so!