JaVa
   

Building a New Task

Now let's start the process of building our new task for the TDSS app and its individual nodes. During the process, we will discuss how to access attributes, dealing with the unless condition and working with nested elements. First, we have the foundation of our example class:

package com.company.ant;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.BuildException;
public class TDSSTask extends org.apache.tools.ant.Task{
 public void execute() throws BuildException {
 try {
 // something
 } catch (Exception e) {
 throw new BuildException(e)
 }
}


Accessing Properties

For each of the attributes within the <TDSS> node, we need to supply a setter method within our Task class. For example, we have an attribute called verbose that needs a setter, which should look like this:

Private boolean verbose = false;
Public void setVerbose(boolean verbose) {
 this.verbose = verbose;
}


As you can see, we've declared a boolean but the only value in the build script is a string. Fortunately, Java uses introspection to obtain the string value from the build script and then automatically expands the string into an appropriate data type. We need to add the following code needs to our class to handle all of the attributes in our task:

 private String TDSSDir = null;
 private boolean displayoutput = false;
 private boolean verbose = true;
 private String outputfile = "";
 public void setTDSSDir(String TDSSDir) {
 this.TDSSDir = TDSSDir;
 }
 public void setVerbose (boolean verbose) {
 this.verbose = verbose ;
 }
 public void setDisplayoutput (boolean displayoutput) {
 this.displayoutput = displayoutput ;
 }
 public void setOutputfile (boolean outputfile) {
 this.outputfile = outputfile ;
 }


At this point we just need to add as many setters as we have attributes in our task. For the moment we are going to ignore the attributes and the <node> element.

Working with <classpath>

As you will notice in the <TDSS> node, we have included a <classpath> element so we can have access to all of the classes available. There are a few specific methods for handling the class path:

 public void setClasspath(Path classpath) {
 this.classpath = classpath;
 }
 public void setClasspathRef(Reference ref) {
 createClasspath().setRefid(ref);
 }


The Ant system will call either the setClasspath or stClasspathRef method, depending on whether a specific path is supplied in the <classpath> element or a reference ID is specified.

Setting Up the Task

Within the execute() method are a few tasks that need to be handled in order to start the process of using the task. Ultimately, what we are trying to do is build a list of attributes and nodes for our app run. All of the node information will be passed to another class, which will launch TDSS, gather information, and produce our output file. With this goal in mind, we must consider how we are going to execute the overall app from our task. The asnwer is the Java class. We are basically going to build up a Java object that relates to a new class called TDSSLauncher. The TDSSLauncher will handle all of the details of running our actual app. Building the Java object will take place in the execute() method. The code to handle our launcher is as follows:

 Java javaTask = null;
 javaTask = (Java) getProject().createTask("java");
 javaTask.setTaskName(getTaskName());
 javaTask.setClassname("com.company.app.TDSSLauncher");
 javaTask.setClasspath(classpath);
 // Add command-line arguments
 javaTask.createArg().setValue("-h");
 javaTask.createArg().setFile(TDDSDir);
 if (outputFile != null) {
 javaTask.createArg().setValue("-o");
 javaTask.createArg().setFile(outputFile);
 }
 if (showoutput) {
 javaTask.createArg().setValue("-d");
 }
 if (verbose) {
 javaTask.createArg().setValue("-v");
 }
 javaTask.setFork(true);
 if (javaTask.executeJava() != 0) {
 throw new BuildException("Error from launching TDSSLauncher");
 }


In this code, we begin by creating a Java object called javaTask, setting its name and, most important, setting the class to be executed. In our case, the class is called TDSSLauncher. We won't discuss TDSSLauncher here, but keep in mind that this class is designed to take all of the options specified in the build script, execute the TDSS app, and produce the appropriate output file. Each of the attributes set in the build script will call individual setters as we defined earlier. The setter just sets private variables within the TDSSTask class. Each of those private variables will be examined, and depending on their value, a command-line option to the TDSSLauncher will be set (or not). All we are doing in the execute() method is examining the attributes set by the user in the build script and executing another class to handle all of the work. The TDSSLauncher class is executed using the executeJava() method of the javaTask object. Notice we called the setFork() method to make sure that we don't stop the execution of our Ant task as well as the entire Ant build process. The TDSSLauncher and subsequently our TDSS app will run in a separate thread.

Using Nested Elements

One thing you will notice between the setters defined above and the <TDSS> element is the fact that we have not yet handled the <node> elements. Our system is designed so that we can have any number of nested <node> elements under the <TDSS> element. Just how does Ant hande this type of situation? When the Ant process encounters a subelement, it attempts to find three different methods within the Task object representing the subelement. One of those methods is called addConfigured<element>. This method is called with an argument of a configured <node> element. But what does that mean? Well, it means that we need to have another class defined called Node with the appropriate setters so Ant can fill out the values when the subelement is encountered. Here's an example of the Node class:

public static class Node implements Serializable {
private int id = 0;
private String username;
private String password;
private int popularity = 0;
private boolean raid = false;
private String ifProperty;
public void setID(int id) {
 this.id = id;
}
public void setUsername(String username) { this.username = username;
}
public void setPassword(String password) {
 this.password = password;
}
public void setPopularity(int popularity) {
 this.popularity = popularity;
}
public void setRaid(boolean raid) {
 this.raid = raid;
}
public void setIf(String ifProperty) {
 this.ifProperty = ifProperty;
}
public int getID() {
 return id;
}
public String getUsername() {
 return username;
}
public String getPassword() {
 return password;
}
public int getPopularity() {
 return popularity;
}
public boolean getRaid() {
 return raid;
}
public String getIf() {
 return ifProperty;
}
}


As you can see, the Node class has all of the appropriate setters for the attributes within the <node> element. Now we must define the addConfiguredNode() method:

public void addConfiguredNode(Node n) {
 if (includeNode(n)) {
 nodes.add(robot);
 }
 }


The Ant process calls addConfiguredNode() each time it encounters a <node> subelement. The Node object passed to the method will be already instantiated and filled with the values from the <node> element's attributes. Of course, we have the if property in the node attribute as well. We need to check this property before doing anything with the node object. The work of checking the if property is performed in the includeNode() method, which is defined as follows:

 private boolean includeNode(Node node) {
 String ifProperty = node.getIf();
 Project p = getProject();
 if (ifProperty != null && p.getProperty(ifProperty) == null) {
 return false;
 }
 return true;
 }


The includeNode() method obtains the boolean value associated with a particular node. It also pulls the ifProperty value from the Project to determine if it was set using –D or within the build script. If the property is not set, then we shouldn't include this node in our app run and thus a false value is returned; otherwise, we return true. Looking back at the addConfiguredNode() method, we find that if the node should be included in the app, we add it to a private variable called nodes. The variable nodes is defined as

 private ArrayList nodes = new ArrayList();


Well, we are getting close to completion. We have been able to obtain the attributes for the <TDSS> elements as well as all of the <node> elements. We've created a Java object that will execute a class called TDSSLauncher using all of the information gained from our own Ant task called TDSS. Our last step in the process is to let the TDSSLauncher have access to the Node list. We do this by serializing the ArrayList of nodes and place the resulting data stream into a file in the directory specified by TDSSDir. We accomplish all this using the execute() method. For example:

 FileOutputStream fo = null;
 ObjectOutputStream oo = null;
 try {
 fo = new FileOutputStream(TDSSDir + "/nodes.txt"); oo = new ObjectOutputStream(fo);
 oo.writeObject(nodes);
 oo.close();
 fo.close();
 } catch(Exception fileE) {
 } 


This code creates a file called nodes.txt within the directory specified by the TDSSDir defined within the <TDSS> Ant task. The ArrayList of nodes will be serialized and placed within the file. The TDSSLauncher app will be able to find the file because we pass the value of the TDSSDir to the class as a command-line option.


JaVa
   
Comments