Im folgenden Beispiel wollen wir ein kleines Doclet schreiben, das Klassen, Methoden und Konstruktoren ausgibt, die das Tag @since 1.8 (bzw. @since 8, was aber eigentlich falsch ist) tragen. So lässt sich leicht ermitteln, was in der Version Java 8 alles hinzugekommen ist. Doclets werden normalerweise von der Kommandozeile aufgerufen und dem javadoc-Tool übergeben. Unser Programm vereinfacht das, indem es direkt das Tool über Java mit dem passenden Parameter aufruft. tools.jar muss dafür im Klassenpfad sein und die Dokumentation ausgepackt am angegeben Ort.
package com.tutego.tools.javadoc; import java.util.*; import java.util.function.Predicate; import com.sun.javadoc.*; import com.sun.tools.javadoc.Main; public class SinceJava8FinderDoclet { public static boolean start( RootDoc root ) { for ( ClassDoc clazz : root.classes() ) processClass( clazz ); return true; } private static void processClass( ClassDoc clazz ) { Predicate<Tag> isJava18 = tag -> tag.text().equals( "8" ) || tag.text().equals( "1.8" ); if ( Arrays.stream( clazz.tags( "since" ) ).anyMatch( isJava18 ) ) System.out.printf( "Neuer Typ %s%n", clazz ); for ( MethodDoc method : clazz.methods() ) if ( Arrays.stream( method.tags( "since" ) ).anyMatch( isJava18 ) ) System.out.printf( "Neue Methode %s%n", method ); for ( ConstructorDoc constructor : clazz.constructors() ) if ( Arrays.stream( constructor.tags( "since" ) ).anyMatch( isJava18 ) ) System.out.printf( "Neuer Konstruktor %s%n", constructor ); for ( FieldDoc field : clazz.fields() ) if ( Arrays.stream( field.tags( "since" ) ).anyMatch( isJava18 ) ) System.out.printf( "Neues Attribut %s%n", field ); } public static void main( String[] args ) { String[] params = { "-quiet", "-XDignore.symbol.file", "-doclet", SinceJava8FinderDoclet.class.getName(), "-sourcepath", "C:/Program Files/Java/jdk1.8.0/src/", // "java.lang", // Nur java.lang "-subpackages", "java:javax" // Alles rekursiv unter java.* und javax.* }; Main.execute( params ); } }
Unsere main(String[])-Methode ruft das JDK-Doclet-Programm über Main.execute(String[]) auf und übergibt die eigene Doclet-Klasse per Parameter – die Argumente von execute(String[]) erinnern an die Kommandozeilenparameter. Das Doclet-Hauptprogramm wiederum ruft unsere start(RootDoc root)-Methode auf – das Gleiche würde auch passieren, wenn das Doclet von außen über javadoc aufgerufen würde. Unser start(RootDoc) läuft über alle ermittelten Typen und übergibt zum Abarbeiten der Innereien die Verantwortung an processClass(ClassDoc). Die Metadaten kommen dabei über diverse XXXDoc-Typen. Ein Precidate zieht den Tag-Test heraus, ein weiterer Einsatz der neuen Java 8 Streams würde das Programm nicht übersichtlicher machen.
Das Programm nutzt ein paar Tricks, um die Ausgabe auf das Wesentliche zu konzentrieren. Der Schalter –quit schaltet den üblichen Ladestatus, der zu Ausgaben wie
Loading source files for package java.lang…
Loading source files for package java.applet…
Loading source files for package java.awt…
führt ab.
Der Schalter -XDignore.symbol.file wiederum unterdrückt Meldungen wie diese hier:
C:\…\src\java\lang\Class.java:57: warning: Unsafe is internal proprietary API and may be removed in a future release
import sun.misc.Unsafe;
^
Die Meldungen landen auf dem System.err-Kanal, sodass sie sich auch mit System.setErr(…) in einem ausgabeverwerfenden Strom geschickt werden können um sie zu unterdrücken.