Buscar
Social
Ofertas laborales ES
« Introduccrión a los motores de plantillas | Main | Creacrión de pools de objetos »
sábado
jun012002

Ant (5): creacrión de tareas propias


Creación de tareas en Ant







Descripción




Como ya hemos visto en otros artículos, Ant ofrece una gran cantidad de tareas predefinidas para realizar distintas acciones. Sin embargo, es posible que en algún momento necesitemos realizar algunas acciones concretas y no haya una acción que se ajuste a nuestras necesidades.



Para solucionar este problema, Ant proporciona al usuario la posibilidad de crear tareas personalizadas escritas en Java.



En este artículo se explica paso a paso la creación de distintos tipos de tareas para Ant.




Creando una tarea sencilla




Vamos a crear una tarea muy sencilla que funciona de manera similar a echo. Se llamará Prueba1 y tendrá un atributo cadena de tipo String.



En un fichero build.xml, se usaría de la siguiente forma:




<target ...>
<Prueba1 cadena="unaCadena"/>
...
</target>



Cada tarea se implementa en (al menos) una clase Java. Todas las tareas extienden org.apache.tools.ant.Task.




import org.apache.tools.ant.*;

public class Tarea1 extends Task {
...
}



Es importante destacar que el nombre de la tarea (Prueba1) y el nombre de la clase que la implementa (Tarea1) no tienen por qué coincidir.




Por cada atributo de la tarea, definiremos un atributo en la clase:




protected String cadena;



En este caso, ambos nombres sí deben coincidir.




Además, definiremos un método de tipo setter, que será invocado por Ant para darle valor al atributo Java:




public void setCadena (String cadena) {
this.cadena = cadena;
}



Existen algunas restricciones en cuanto al nombre del método: debe empezar por set, la siguiente letra debe ser mayúscula y las demás minúsculas.




Por último, definimos un método execute, de la siguiente manera:




public void execute () throws BuildException {
...
}



En el cuerpo del método pondremos todo aquello que queremos que la tarea haga.




Aquí está el ejemplo completo listo para ser compilado.




A continuación compilamos la tarea. El paquete org.apache.tools.ant se encuentra en el fichero $ANT_HOME/lib/ant.jar.




javac -classpath $ANT_HOME/lib/ant.jar Tarea1.java



Modificando y utilizando el fichero build.xml




Sólo queda añadir al principio del project, una linea taskdef para relacionar el nombre de la tarea con el nombre completo (incluyendo los paquetes, si los hay) de la clase que la implementa:




<project ...>
<taskdef name="Prueba1" classname="Tarea1"/>
...
</project ...>



Aquí está el primer build.xml.




Para poder utilizar la nueva tarea, Ant debe ser capaz de encontrar la clase que la implementa. En la documentación oficial se recomienda ampliar la variable de entorno CLASSPATH (en este caso, con el directorio donde está situada Tarea1.class). Así, la nueva tarea podría utilizarse con:




ant -buildfile build1.xml



Alternativamente, es posible invocar a Ant sin tener que modificar el classpath actual. Suponiendo que Tarea1.class esté en el directorio actual, podríamos usar algo como:




java -classpath $ANT_HOME/lib/ant.jar:. org.apache.tools.ant.Main -buildfile build1.xml



Ésta es la salida obtenida:




Buildfile: build1.xml

Prueba1:
[Prueba1]
[Prueba1] Tarea1 ...
[Prueba1] cadena = unaCadena
[Prueba1] Fin de Tarea1
[Prueba1]
[Prueba1]
[Prueba1] Tarea1 ...
[Prueba1] cadena = otraCadena
[Prueba1] Fin de Tarea1
[Prueba1]

BUILD SUCCESSFUL

Total time: 0 seconds



Por último, también es posible añadir nuestra tarea al conjunto de tareas predefinidas de Ant, de modo que no sea necesario indicar el elemento taskdef. Para ello es necesario disponer del código fuente de Ant y añadir al fichero src/main/org/apache/tools/ant/taskdefs/defaults.properties una linea indicando el nombre de la tarea, tal cual ha de ser usado y el nombre completo de la clase que la implementa. Por ejemplo:




unatarea=com.javahispano.tareas.unatarea



Por supuesto, es necesario que esta tarea sea accesible por Ant, bien modificando la variable de entorno CLASSPATH, bien invocando org.apache.tools.ant.Main con el parámetro classpath adecuado.




La clase org.apache.tools.ant.Task




La clase Task es la clase base de todas las tareas. En ella se definen varios atributos y métodos que conviene conocer. Entre otras cosas, es posible conocer atributos del proyecto (nombre, tareas predefinidas que puede utilizar, propiedades, etc.), de los objetivos del proyecto (cuáles son, qué tareas contienen, etc.) y de la tarea que se está ejecutando. Por ejemplo:



  • Obtener el nombre del proyecto: getProject().getName();
  • Obtener el nombre del objetivo al que pertenece la tarea: getTarget().getName();
  • Obtener el nombre de la tarea: getTaskName();



Aunque la mejor referencia es el API, aquí hay un ejemplo de cómo usar alguno de ellos. Aquí está el fichero build.




A modo de curiosidad, las propiedades definidas en el fichero build no se incluyen en las obtenidas con el Project.getUserProperties sino con Project.getProperties.




Más tipos de datos




En el primer ejemplo sólo hemos usado atributos del tipo String, aunque Ant admite además tipos primitivos, la clase File, la clase Class y cualquier otra clase que tenga un constructor (público) con un único argumento de tipo String (por ejemplo, StringBuffer).




Los argumentos de tipo File permiten indicar un fichero o un directorio (mediante una ruta absoluta o relativa). Por ejemplo, el atributo todir de la tarea Copy, internamente es de tipo File.



Cuando en una tarea se declara un atributo de tipo File, se instancia un objeto File con el nombre de fichero indicado en el atributo correspondiente. Se tiene la garantía de que cuando se ejecuta el método execute, los métodos set ya se han ejecutado y los atributos de tipo File ya han sido instanciados. El siguiente es un ejemplo de uso de este tipo de atributos en una tarea de un fichero build:




<target ...>
<unaTarea archivo="build.xml"/>
...
</target>



En la clase correspondiente, tendríamos algo como:




protected File archivo;

public void setArchivo (File archivo) {
this.archivo = archivo;
}

public void execute () throws BuildException {
System.out.println("Ruta absoluta: " + archivo.getAbsolutePath());
}



Así, el valor indicado en el atributo ("build.xml") será utilizado en una invocación al método setArchivo. Antes de ejecutar el método execute, se habrá realizado una instanciación equivalente a:




new File(archivo)



de forma que archivo está correctamente inicializado con el valor indicado en la tarea del fichero build.




También es posible definir argumentos cuyo atributo correspondiente en la clase que implementa la tarea pertenezca a una clase que tiene un constructor que recibe como único parámetro un objeto String.




Básicamente, se tienen las mismas garantías que en el caso anterior, de manera que cuando se ejecuta el método execute, el atributo correspondiente ya ha sido instanciado con el valor dado en el argumento de la tarea.




Por ejemplo:




<target ...>
<unaTarea buffer="cadena"/>
...
</target>



y en la clase correspondiente:




protected StringBuffer buffer;

public void setBuffer (StringBuffer buffer) {
this.buffer = buffer;
}

public void execute throws BuildException () {
buffer.append(" cadena2");
System.out.println("StringBuffer:" + buffer.toString());
}



Sabemos que cuando se ejecute el método execute, el atributo buffer de la clase estará inicializado como si se hubiera ejecutado la siguiente instanciación:




new Stringbuffer(buffer)



La ejecución de esta tarea mostraría por pantalla la cadena "cadena cadena2".




Aquí hay un ejemplo de uso de los atributos de tipo File y StringBuffer. Aquí está el correspondiente fichero build.




Tareas que admiten texto




Algunas tareas, como echo, admiten un cuerpo no vacío, en el que hay texto:




<target ...>
<echo>
Primera linea del cuerpo
Una linea más
Otra linea
</echo>
</target>



La forma de implementar este tipo de tareas se basa en definir un método con el siguiente perfil:




public void addText (String)



No existen otras restricciones en cuanto a atributos necesarios o implementación del método anterior. Dentro del cuerpo de este método, podemos realizar cualquier tipo de acción, aunque lo más indicado es guardar el texto recibido en algún atributo, procesarlo de alguna manera si es necesario y utilizarlo posteriormente durante la ejecución del método execute.




Aquí hay un ejemplo de uso de tareas con texto interno. Aquí está el correspondiente fichero build.




Usando tipos enumerados




Algunas tareas tienen atributos enumerados, esto es, atributos que deben contener un valor de entre un conjunto de ellos. Por ejemplo, el atributo access de la tarea Javadoc es un atributo de tipo enumerado (puede tener los valores public, protected, package o private):




<target ...>
<javadoc
...
access="public"
/>
</target>



Es posible comprobar estos atributos de dos formas distintas. La primera es la forma obvia: comparar el valor recibido como argumento del método setter contra un conjunto de posibles valores:




protected String cadena;

public void setCadena (String enumerado) {
// Comprobar los posibles valores
if (enumerado.equals("valor1") || enumerado.equals("valor2") || ...)
this.cadena = enumerado;
else
throw new Exception ("Valor no permitido: " + enumerado);

}



Sin embargo, existe una forma más cómoda de proceder. Consiste en definir una tarea (por ejemplo interna) que extienda org.apache.tools.ant.types.EnumeratedAttribute, como:




public static class MiTipoEnumerado extends EnumeratedAttributes {
public String [] getValues () {
return new String [] {"valor1", "valor2", "valor3"};
}
}



El método getValues es abstracto en la clase EnumeratedAttributes. En él se declaran los posibles valores. La clase EnumeratedAttributes tiene otro método, setValue, declarado como final, donde se comprueba la pertenencia del valor indicado en el fichero build al conjunto de valores indicado en el método getValues. De esta forma, se nos garantiza que el valor recibido en el método setEnumerado es un valor correcto. En el caso de que el valor indicado no sea correcto, desde el método setValue de EnumeratedAttributes se lanzará una excepción señalando que el valor indicado no es válido.



Es importante que la clase se declare con los modificadores public y static. De otra forma, se obtendrá un error en tiempo de ejecución (concretamente, una excepción de tipo java.lang.InstantiationException).




El argumento del método setter a usar será de este tipo:




public void setCadena (MiTipoEnumerado conjunto) {
// Obtener el valor indicado en el fichero build
this.cadena = conjunto.getValue();
}



Esta forma de proceder presenta la ventaja de que el control del valor utilizado se reduce únicamente a declarar el conjunto de valores permitidos. El resto corre a cargo de la clase EnumeratedAttributes.



Según el caso, declarar la clase como interna puede ser suficiente. Por ejemplo, cuando el conjunto de valores es muy concreto y específico. En otros casos, puede ser útil definirla como pública, en un fichero aparte e incluso en un paquete aparte.




Aquí hay un ejemplo de uso con el correspondiente fichero build. La ejecución del único objetivo que hay produce un error de ejecución debido a que el valor "valor4" no pertenece al conjunto de valores válidos declarado en la clase interna MiTipoEnumerado.




Tareas con elementos anidados



La mayoría de tareas predefinidas aceptan elementos anidados. Por ejemplo, la tarea javac admite un parámetro src para indicar los ficheros fuente.



Para manejar estos elementos hay que implementar métodos createXXX o addXXX. Por la falta de documentación oficial en la distribución actual (1.4.1) y su complejidad, el uso de este tipo de elementos será descrito en un próximo artículo.




El ciclo de vida de una tarea




Cada tarea indicada en un fichero build sigue el siguiente ciclo de vida:





  1. La tarea es instanciada invocando el constructor sin argumentos, cuando Ant parsea el fichero build.xml. Todas las tareas indicadas con taskdef son instanciadas, incluso si posteriormente no llegan a ser utilizadas.
  2. La tarea obtiene referencias al projecto al que pertenece y a su location (posición dentro del fichero build) mediante los atributos project y location heredados de la clase Task.
  3. Si el usuario indicó un atributo id en la instanciación de la tarea (en el fichero build), en el objeto proyecto se guarda (en tiempo de parseo) una referencia a esta tarea, mediante su método addReference(String, Object). Es posible obtener estas referencias mediante el método getReferences del objeto Project.
  4. La tarea obtiene una referencia al objetivo al que pertenece mediante el atributo target heredado de la clase Task.
  5. El método init es invocado en tiempo de parseo.
  6. En tiempo de parseo, todos los elementos hijos de la tarea (en el fichero build, los elementos XML anidados) son creados mediante los métodos createXXX o instanciados y añadidos a esta tarea mediante los métodos addXXX.
  7. En tiempo de ejecución, los métodos setXXX son ejecutados para dar valor a los atributos de la tarea.
  8. En tiempo de ejecución, el método addText es ejecutado, para recuperar el texto contenido en el interior del elemento XML que representa a la tarea.
  9. En tiempo de ejecución, los atributos de los elementos hijos son establecidos mediante sus correspondientes métodos setXXX.
  10. El método execute es ejecutado al menos una vez.



En cuanto al último punto, es importante destacar que el método execute puede invocarse varias veces. En el siguiente ejemplo, el método execute de la tarea t es ejecutado exactamente 2 veces:




<?xml version="1.0" encoding="iso-8859-1"?>
<project name="variasveces" default="tres">
<target name="uno">
<echo message="uno"/>
</target>

<target name="dos" depends="uno">
<echo message="dos"/>
</target>

<target name="tres" depends="dos">
<echo message="tres"/>
</target>
</project>



Integración de Ant con editores y entornos externos.




Durante la ejecución de Ant, se generan eventos que pueden ser dirigidos a otras aplicaciones. jEdit es un ejemplo de editor que puede trabajar estrechamente con Ant.



En un próximo artículo incluiremos información adicional sobre esta característica de Ant.




Puedes bajarte todo el código y los ficheros build de éste artículo en un solo fichero de aquí




Conclusión



Ant es una potente herramienta de desarrollo que proporciona una gran cantidad de tareas estándar para realizar multitud de acciones comunes. Su flexibilidad va más allá, al ofrecer un mecanismo sencillo pero potente de ampliación del juego básico de tareas.



En este artículo se han cubierto los primeros pasos para poder desarrollar nuestras propias tareas de Ant, a través de varios ejemplos sencillos que el lector sabrá ampliar y personalizar para adaptarlos a sus necesidades.

















Emili Miedes es Ingeniero en Informática y trabaja de momento
para el Instituto Tecnológico de Informática de la Universidad
Politécnica de Valencia desarrollando en Java.


Desarrollador opensource en sus ratos libres (buscar por emiedes en Sourceforge) anda buscando colaboración para acabar todo lo que empieza.




Para cualquier duda o tirón de orejas, e-mail a:
emiedes_ARROBA_iti.upv.es




Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.
Comentarios deshabilitados
Comentarios deshabilitados en esta noticia.