Doclets
Qué es un Doclet en java[editar]
Un Doclet es la clase encargada de generar la documentación de nuestros fuentes java a partir de sus comentarios.
Cuando hacemos
javadoc fuentes.java
javadoc recoge la información en los fuentes de java y se la pasa a una clase java llamada StandardDoclet. Esta clase es la encargada de generar la documentación de javadoc tal cual la conocemos. Tienes un ejemplo de documentación generada por javadoc en javadoc de librería gráfica en java
Sin embargo, es fácil cambiar esta clase StandardDoclet por defecto por otra. Basta con hacerla, compilarla y luego llamar a javadoc asi
javadoc -doclet MiClase -docletpath PathDeMiClase fuentes.java
Implementar un Doclet[editar]
En versiones de java 11 o superior, para implementar un Doclet debemos implementar la interface jdk.javadoc.doclet.Doclet
. En java 11 o anteriores debíamos hacerlo usando las clases del paquete com.sun.javadoc
, que ha sido eliminado en java 17. Java 11 contiene ambos, aunque marca como obsoleto com.sun.javadoc
Vamos con la versión moderna. La interface jdk.javadoc.doclet.Doclet
tiene varios métodos que explicamos a continuación.
Tienes el ejemplo completo en DocletExample.java
Método init()[editar]
javadoc llamará a este método en el inicio. No es necesario hacer nada especial, sólo lo que nuestro Doclet necesite.
@Override
public void init(Locale locale, Reporter reporter) {
reporter.print(Diagnostic.Kind.NOTE, "Doclet using locale: " + locale);
this.reporter = reporter;
}
Nos pasa el Locale con el que se quiere generar la documentación javadoc y nos pasa una clase Reporter
que sólo sirve para que escribamos en ella información opcional sobre qué está haciendo nuestro Doclet para mostrar al usuario cuando ejecute javadoc. En el ejemplo, simplemente mostramos qué Locale nos están pasando. El primer parámetro Diagnostic.Kind.NOTE
es el tipo de información que estamos pasando. Tenemos opciones de ERROR, WARNING, etc.
Podemos dejar el método vacío si no queremos sacar este tipo de información.
Método run()[editar]
Este es el método al que javadoc llamará cuando quiera que se genere la documentación.
@Override
public boolean run(DocletEnvironment docEnv) {
reporter.print(Diagnostic.Kind.NOTE, "myCustomOption: " + myCustomOption);
DocTrees docTrees = docEnv.getDocTrees();
final Set<? extends Element> specifiedElements = docEnv.getSpecifiedElements();
specifiedElements.forEach(element -> {
printEnclosedElements(docTrees, element);
});
return true;
}
Nos pasa como parámetro un DocletEnvironment
. De él podemos obtener todo lo necesario para saber qué paquetes, clases, métodos, comentarios, etc hay en los fuentes. A partir de esta información, podemos generar nuestra propia documentación o hacer el análisis que queramos de los fuentes.
Veamos algunos detalles
Acceder a paquetes, clases, métodos y atributos[editar]
docEnv.getDocTrees()
nos devuelve un árbol con todos los comentarios de los fuentes java, separados por elementos (clase, método, parámetro, etc). Lo necesitaremos si queremos acceder a los comentarios en el código.- docEnv.getSpecifiedElements() Nos devuelve los elementos que se han especificado en la línea de comandos. Un elemento es una clase, un paquete, etc. Si miras el comando javadoc que pusimos al principio javadoc fuentes.java, el elemento sería la clase contenida en ese fuentes.java.
printEnclosedElements()
es un método que hemos hecho para sacar por pantalla datos de los elementos que nos han pasado. Vemos los detalles.
public void printEnclosedElements (DocTrees tree, Element e){
System.out.println(e.getKind() +": "+e);
printComments(tree, e);
final List<? extends Element> enclosedElements = e.getEnclosedElements();
enclosedElements.forEach(element -> {
printEnclosedElements(tree, element);
});
}
Es un método recursivo, es decir, se llama a sí mismo.
Lo primero que hace, con System.out.println()
es sacar por pantalla el elemento e
que nos han pasado (su nombre) y su tipo e.getKind()
(si es una clase, un método, un constructor, ...). Algunos ejemplos de salida de esta línea podrían ser
PACKAGE: com.chuidiang CLASS: com.chuidiang.App CONSTRUCTOR: App() METHOD: main(java.lang.String[]) ...
es decir, a lo largo de la ejecución, han llegado a esta línea elementos package (com.chuidiang), clases (com.chuidiang.App), contructores (App()), métodos (main(java.lan.String[])), etc.
La llamada a printComments()
es otro método que nos hemos hecho. Sacaría por pantalla los comentarios en el código fuente del elemento, así como los "tag" en el comentario (cosas como @author, @deprecated, etc). Vemos este método más adelante.
Con e.getEnclosedElements()
obtenemos los "hijos" del elemento en curso. Por ejemplo, si el elemento en curso es un paquete, obtendríamos las clases, si es una clase, sus métodos y atributos. Así que un bucle para recorrerlos y llamada recursiva al método printEnclosedElements()
.
Acceder a los comentarios[editar]
Veamos al parte de los comentarios
public void printComments(DocTrees trees, Element e) {
DocCommentTree docCommentTree = trees.getDocCommentTree(e);
if (docCommentTree != null) {
System.out.println(" body comment : " + docCommentTree.getFullBody());
System.out.println(" tags : " + docCommentTree.getBlockTags());
} else {
System.out.println(" No comments");
}
}
Como hemos comentado, esta parte es para sacar por pantalla los comentarios en el código fuente.
trees.getDocCommentTree()
devuelve los comentarios del elemento que se le pasa como parámetro.docCommentTree.getFullBody()
obtiene el texto del comentario.docCommentTree.getBlockTags()
obtiene las eqituetas (@autor, @deprectaed, etc) en el comentario.
Si nuestra clase App
tiene el siguiente comentario
/**
* Hello world!
*
* @author Chuidiang
*/
public class App
{
...
}
La salida de este método sería similar a la siguiente
body comment : Hello world! tags : @author Chuidiang
getName()[editar]
Tenemos que implementar el método getName()
. Este es fácil, basta devolver el nombre que queramos para nuestro Doclet, sin espacios.
@Override
public String getName() {
return "Example";
}
getSupportedOptions()[editar]
Si queremos poder pasar algunas opciones propias a nuestro Doclet desde la línea de comandos, por ejemplo
javadoc -misopciones a b fuente.java
debemos implementar este método devolviendo un Set<Option>
de opciones. La interface Option
tiene unos cuantos métodos a implementar, aunque algunos son informativos. Ponemos el código y la explicación en el comentario
new Option() {
/**
* Una descripción de para qué sirve nuestra opción para enseñársela al usuario.
* @return
*/
@Override
public String getDescription() {
return "my custom option";
}
/**
* Qué tipo de opcion. Hay STANDARD, EXTENDED y OTHER. Habitualmente usaremos
* STANDARD
* @return
*/
@Override
public Option.Kind getKind() {
return Option.Kind.STANDARD;
}
/**
* Debemos devolver todos los pasibles alias para nuestra opción. Por ejemplo
* puede ser "-myCustomOption" o su forma abreviada "-co" o su forma expandida
* "-my-custom-option"
*/
@Override
public List<String> getNames() {
return Arrays.asList(
"-myCustomOption", "-co", "-my-custom-option");
}
/**
* Indicamos cuantos valores lleva detrás nuestra opción. Por ejemplo, si en
* línea de comandos ponemos
* javadoc -co valor1 valor2 valor3
* y en este método devolvermos 1, solo nos pasarán valor1 como parte de nuestra opción.
* Si ponemos 2, nos pasaraán valor1 y valor2, etc.
* @return
*/
@Override
public int getArgumentCount() {
return 1;
}
/**
* Una descripción de qué tipo de valor esperamos para nuestra opción. Algo com "path de los
* fuentes", "autor", "comentario". Es para mostrarle al usuario y que sepa qué tipo de dato
* espera nuestra opción.
* @return
*/
@Override
public String getParameters() {
return "any text";
}
/**
* Cuando se ejecuta el comando javadoc, nos pasarán aquí para cada una de nuestras
* opciones los valores que el usuario ha escrito.
* Debemos guardarlos para tenerlos disponibles y usarlos como hayamos previsto y
* devolver true si está todo bien.
* @param opt
* @param arguments
* @return
*/
@Override
public boolean process(String opt, List<String> arguments) {
if (Arrays.asList(
"-myCustomOption", "-co", "-my-custom-option").contains(opt)){
myCustomOption = arguments.get(0);
System.out.println("My Custom Option : "+myCustomOption);
}
return true;
}
}
El método getSupportedOptions()
de Doclet
debe devolver un Set<Option>
con todas las opciones que queramos que nuestro Doclet
tenga.
Ejecución del Doclet[editar]
Debemos compilar nuestro Doclet y bien tener el .class, bien meterlo en un jar. Vamos con esta segunda opción. La ejecución sería como la siguiente
javadoc -doclet com.chuidiang.doclet.DocletExample -docletpath maven-doclet-1.0-SNAPSHOT.jar -co Pepe \\ --source-path ..\..\maven-javadoc\src\main\java\ -subpackages com.chuidiang
Vamos por partes
-doclet
para indicar el nombre de nuestra clase Doclet, incluyendo en qué paquete está.-docletpath
para indicar el jar donde está empaquetado nuestro Doclet-co
es la opción que dijimos que queríamos que se le pudiera pasar a nuestro Doclet. Recibiremos Pepe. Ya es cosa nuestra qué hagamos con esa opción y valor.--source-path
es una opción estándar de javadoc. Sirve para indicar el directorio de los fuentes de los que queremos sacar el javadoc.--subpackages
. También es una opción estándar de javadoc. Indica de qué paquetes queremos sacar javadoc, incluyendo subpaquetes. Estos son los elementos que nos pasarán a nuestro Doclet.
Ejemplo de salida[editar]
Si ejecutamos nuestro Doclet sobre una clase App de java con un Hola mundo, que esté comentada, tendermos algo como lo siguiente
Note: Doclet using locale: es_ES My Custom Option : Pepe Loading source files for package com.chuidiang... Constructing Javadoc information... Note: myCustomOption: Pepe PACKAGE: com.chuidiang No comments CLASS: com.chuidiang.App body comment : Hello world! tags : @author Chuidiang CONSTRUCTOR: App() No comments METHOD: main(java.lang.String[]) body comment : El main tags : @param args argumentos
Doclets con Maven[editar]
Puedes utilizar tus doclet con maven-javadoc-plugin en maven.
Doclets de terceros[editar]
¿Y por qué no ir más allá?. Un Doclet no tiene porqué generar documentación. Con la información de los fuentes recibida puede hacer cualquier cosa. Hay Doclet también que revisan el estilo de código a ver si cumple las convenciones, que generan esqueletos de test para JUnit, que a partir de un bean java generan los EJBs necesarios, que generan ficheros html con los fuentes y la sintaxis coloreada, etc, etc, etc.
En http://www.doclet.com hay un montón de clases "Doclet" ya hechas que hacen montones de cosas, como generar la documentación añadiéndole un diagrama de clases automáticamente, generarla en pdf, xml o rtf, etc. También con opciones como las que acabamos de comentar que no son solo para generar documentación.
Enlaces[editar]
- La documentación de openjdk en inglés: https://openjdk.org/groups/compiler/using-new-doclet.html
- Varios doclets para bajarse, unos gratis, otros de pago : http://www.doclet.com/