{"id":4197,"date":"2018-07-21T11:51:01","date_gmt":"2018-07-21T09:51:01","guid":{"rendered":"http:\/\/www.tutego.de\/blog\/javainsel\/?p=4197"},"modified":"2018-07-21T11:51:01","modified_gmt":"2018-07-21T09:51:01","slug":"eigene-doclets-programmatisch-ausfuehren-was-ist-neu-in-java-10","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2018\/07\/eigene-doclets-programmatisch-ausfuehren-was-ist-neu-in-java-10\/","title":{"rendered":"Eigene Doclets programmatisch ausf\u00fchren &#8211; was ist neu in Java 10?"},"content":{"rendered":"<p>Das javadoc-Tool hat die Aufgabe, den Java-Quellcode auszulesen und die Javadoc-Kommentare zu extrahieren. Was dann mit den Daten passiert, ist die Aufgabe eines Doclets. Das Standard-Doclet von Oracle erzeugt die bekannte Struktur auf verlinkten HTML-Dateien, aber es lassen sich auch eigene Doclets schreiben, um etwa in Javadoc-Kommentaren enthaltene Links auf Erreichbarkeit zu pr\u00fcfen oder UML-Diagramme aus den Typbeziehungen zu erzeugen. Hervorzuheben ist JDiff (http:\/\/javadiff.sourceforge.net\/), was Differenzen in unterschiedlichen Versionen der Java-Dokumentation \u2013 wie neu hinzugef\u00fcgte Methoden \u2013 erkennt und meldet.<\/p>\n<p>Die Doclet-API ist Teil vom JDK, und deklariert Typen und Methoden, mit denen sich Module, Pakete, Klassen, Methoden usw. und deren Javadoc-Texte erfragen lassen. Allerdings gibt es zwei Doclet-APIs:<\/p>\n<ul>\n<li>Im Paket javadoc.doclet (Modul jdk.javadoc) ist die aktuelle API.<\/li>\n<li>Im Paket sun.javadoc ist die mittlerweile deprecated API, allerdings einfacher zu nutzen.<\/li>\n<\/ul>\n<h4>Beispiel<\/h4>\n<p>Im folgenden Beispiel wollen wir ein kleines Doclet schreiben, das Klassen, Attribute, Methoden und Konstruktoren ausgibt, die das Javadoc-Tag @since 10 tragen. So l\u00e4sst sich leicht ermitteln, was in der Version Java 10 alles hinzugekommen ist. Doclets werden normalerweise von der Kommandozeile aufgerufen und dem javadoc-Tool \u00fcbergeben, doch da es mit der Tool-Schnittstelle auch selbst geht, k\u00f6nnen wir ein Programm mit main(\u2026)-Methode schreiben, das die Neuerungen auf der Konsole ausgibt.<\/p>\n<pre>package com.tutego.insel.tools;\r\n\r\n\r\n\r\n\r\nimport java.io.IOException;\r\n\r\nimport java.nio.file.*;\r\n\r\nimport java.util.Arrays;\r\n\r\nimport java.util.List;\r\n\r\nimport java.util.function.Predicate;\r\n\r\nimport java.util.stream.Collectors;\r\n\r\nimport javax.tools.*;\r\n\r\nimport com.sun.javadoc.*;\r\n\r\n\r\n\r\n\r\n@SuppressWarnings( \"deprecation\" )\r\n\r\npublic class FindSinceTagsInJavadoc {\r\n\r\n\r\n\r\n\r\n\u00a0 public static class TestDoclet {\r\n\r\n\r\n\r\n\r\n\u00a0\u00a0\u00a0 public static boolean start( RootDoc root ) {\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Arrays.stream( root.classes() ).forEach( TestDoclet::processClass );\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 return true;\r\n\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\r\n\r\n\r\n\u00a0\u00a0\u00a0 private static void processClass( ClassDoc clazz ) {\r\n\r\n\r\n\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Predicate&lt;Doc&gt; hasSinceTag = doc -&gt; Arrays.stream( doc.tags( \"since\" ) )\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .map( Tag::text ).anyMatch( \"10\"::equals );\r\n\r\n\r\n\r\n\r\n\u00a0\u00a0\u00a0 \u00a0\u00a0if ( hasSinceTag.test( clazz ) )\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 System.out.printf( \"%s -- Neuer Typ%n\", clazz );\r\n\r\n\r\n\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Arrays.stream( clazz.fields() ).filter( hasSinceTag )\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .forEach( f -&gt; System.out.printf( \"%s -- Neues Attribut%n\", f ) );\r\n\r\n\r\n\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Arrays.stream( clazz.methods() ).filter( hasSinceTag )\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .forEach( m -&gt; System.out.printf( \"%s -- Neue Methode%n\", m ) );\r\n\r\n\r\n\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Arrays.stream( clazz.constructors() ).filter( hasSinceTag )\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .forEach( c -&gt; System.out.printf( \"%s -- Neuer Konstruktor%n\", c ) );\r\n\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0 }\r\n\r\n\r\n\r\n\r\n\u00a0 public static void main( String[] args ) throws IOException {\r\n\r\n\r\n\r\n\r\n\u00a0\u00a0\u00a0 Path zipfile = Paths.get( System.getProperty( \"java.home\" ), \"lib\/src.zip\" );\r\n\r\n\u00a0\u00a0\u00a0 try ( FileSystem srcFs = FileSystems.newFileSystem( zipfile, null ) ) {\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Predicate&lt;Path&gt; filesToIgnore = p -&gt;\u00a0 p.toString().endsWith( \".java\" ) &amp;&amp;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ! p.toString().startsWith( \"\/javafx\" ) &amp;&amp;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ! p.toString().startsWith( \"\/jdk\" ) &amp;&amp;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0! p.toString().endsWith( \"module-info.java\" ) &amp;&amp;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ! p.toString().endsWith( \"package-info.java\" );\r\n\r\n\r\n\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 List&lt;Path&gt; paths = Files.walk( srcFs.getPath( \"\/\" ) )\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .filter( filesToIgnore ).collect( Collectors.toList() );\r\n\r\n\r\n\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 DocumentationTool tool = ToolProvider.getSystemDocumentationTool();\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 try ( StandardJavaFileManager fm = tool.getStandardFileManager( null, null, null ) ) {\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Iterable&lt;? extends JavaFileObject&gt; files = fm.getJavaFileObjectsFromPaths( paths );\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 tool.getTask( null, fm, null, TestDoclet.class, List.of( \"-quiet\" ), files ).call();\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0 }\r\n\r\n}<\/pre>\n<p>Unsere main(\u2026)-Methode bindet zun\u00e4chst das ZIP-Archiv vom JDK mit den Quellen als Dateisystem ein. Im n\u00e4chsten Schritt durchsuchen wir das Dateisystem nach den passenden Quellcodedateien und sammeln sie in eine Liste von Pfaden. Jetzt kann das DocumentationTool mit unserem Doclet konfiguriert und aufgerufen werden. call() startet das Parsen aller Quellen und f\u00fchrt anschlie\u00dfend zum Aufruf von start(RootDoc) unseres Doclets.<\/p>\n<p>In unserer start(\u2026)-Methode laufen wir \u00fcber alle ermittelten Typen und rufen dann processClass(ClassDoc) auf. Dort passiert der eigentliche Test. Die Metadaten kommen dabei \u00fcber diverse XXXDoc-Typen. Ein Predicate zieht den Tag-Test heraus.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Das javadoc-Tool hat die Aufgabe, den Java-Quellcode auszulesen und die Javadoc-Kommentare zu extrahieren. Was dann mit den Daten passiert, ist die Aufgabe eines Doclets. Das Standard-Doclet von Oracle erzeugt die bekannte Struktur auf verlinkten HTML-Dateien, aber es lassen sich auch eigene Doclets schreiben, um etwa in Javadoc-Kommentaren enthaltene Links auf Erreichbarkeit zu pr\u00fcfen oder UML-Diagramme [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","_links_to":"","_links_to_target":""},"categories":[1],"tags":[],"class_list":["post-4197","post","type-post","status-publish","format-standard","hentry","category-allgemein"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/4197","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/comments?post=4197"}],"version-history":[{"count":1,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/4197\/revisions"}],"predecessor-version":[{"id":4198,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/4197\/revisions\/4198"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=4197"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=4197"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=4197"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}