You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by sb...@apache.org on 2001/08/01 12:22:18 UTC

cvs commit: jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata MMetricsStreamHandler.java MMetrics.java MAuditStreamHandler.java MAudit.java AbstractMetamataTask.java

sbailliez    01/08/01 03:22:18

  Added:       src/main/org/apache/tools/ant/taskdefs/optional/metamata
                        MMetricsStreamHandler.java MMetrics.java
                        MAuditStreamHandler.java MAudit.java
                        AbstractMetamataTask.java
  Log:
  New Tasks for Metamata Audit and Metamata Metrics.
  Since Metamata was acquired by Webgain, Quality Analyzer 2.0 is also compatible with them.
  I'm using them for a while and they were requested by Garrick Olson, Garrick.Olson@Aceva.com
  
  Revision  Changes    Path
  1.1                  jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java
  
  Index: MMetricsStreamHandler.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2000 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Ant", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  package org.apache.tools.ant.taskdefs.optional.metamata;
  
  
  import org.xml.sax.*;
  import org.xml.sax.helpers.*;
  import javax.xml.transform.*;
  import javax.xml.transform.stream.*;
  import javax.xml.transform.sax.*;
  import java.util.*;
  import java.io.*;
  import java.text.*;
  
  import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
  import org.apache.tools.ant.Task;
  import org.apache.tools.ant.Project;
  
  /**
   * A handy metrics handler. Most of this code was done only with the
   * screenshots on the documentation since the evaluation version as
   * of this writing does not allow to save metrics or to run it via
   * command line. 
   * <p>
   * This class can be used to transform a text file or to process the
   * output stream directly.
   *
   * @author  <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
   */
  public class MMetricsStreamHandler implements ExecuteStreamHandler {
  
      /** CLASS construct, it should be named something like 'MyClass' */
      protected final static String CLASS = "class";
      
      /** package construct, it should be look like 'com.mycompany.something' */
      protected final static String PACKAGE = "package";
      
      /** FILE construct, it should look like something 'MyClass.java' or 'MyClass.class' */
      protected final static String FILE = "file";
      
      /** METHOD construct, it should looke like something 'doSomething(...)' or 'doSomething()' */
      protected final static String METHOD = "method";
      
      protected final static String[] ATTRIBUTES = { "name", "vg", "loc",
      "dit", "noa", "nrm", "nlm", "wmc", "rfc", "dac", "fanout", "cbo", "lcom", "nocl"
      };
      
      /** reader for stdout */
      protected InputStream metricsOutput;
      
      /**
       * this is where the XML output will go, should mostly be a file
       * the caller is responsible for flushing and closing this stream
       */
      protected OutputStream xmlOutputStream;
  
      /** metrics handler */
      protected TransformerHandler metricsHandler;
      
      /** the task */
      protected Task task;
  
      /**
       * the stack where are stored the metrics element so that they we can
       * know if we have to close an element or not.
       */
      protected Stack stack = new Stack();
            
      /** initialize this handler */
      MMetricsStreamHandler(Task task, OutputStream xmlOut){
          this.task = task;
          this.xmlOutputStream = xmlOut;
      }
  
      /** Ignore. */
      public void setProcessInputStream(OutputStream p1) throws IOException {
      }
  
      /** Ignore. */
      public void setProcessErrorStream(InputStream p1) throws IOException {
      }
  
      /** Set the inputstream */
      public void setProcessOutputStream(InputStream is) throws IOException {
              metricsOutput = is;
      }
  
      public void start() throws IOException {
          // create the transformer handler that will be used to serialize
          // the output.
          TransformerFactory factory = TransformerFactory.newInstance();
          if ( !factory.getFeature(SAXTransformerFactory.FEATURE) ){
              throw new IllegalStateException("Invalid Transformer factory feature");
          }
          try {
              metricsHandler = ((SAXTransformerFactory)factory).newTransformerHandler();
              metricsHandler.setResult( new StreamResult( new OutputStreamWriter(xmlOutputStream, "UTF-8")) );
              Transformer transformer = metricsHandler.getTransformer();
              transformer.setOutputProperty(OutputKeys.INDENT, "yes");
              
              // start the document with a 'metrics' root
              metricsHandler.startDocument();
              AttributesImpl attr = new AttributesImpl();
              attr.addAttribute("", "company", "company", "CDATA", "metamata");
              metricsHandler.startElement("", "metrics", "metrics", attr);
  
              // now parse the whole thing
              parseOutput();
  
          } catch (Exception e){
              e.printStackTrace();
              throw new IOException(e.getMessage());
          }
      }
  
      /**
       * Pretty dangerous business here. 
       */
      public void stop() {
          try {
              // we need to pop everything and close elements that have not been
              // closed yet.
              while ( stack.size() > 0){
                  ElementEntry elem = (ElementEntry)stack.pop();
                  metricsHandler.endElement("", elem.getType(), elem.getType());
              }
              // close the root
              metricsHandler.endElement("", "metrics", "metrics");
              // document is finished for good
              metricsHandler.endDocument();
          } catch (SAXException e){
              e.printStackTrace();
              throw new IllegalStateException(e.getMessage());
          }
      }  
  
      /** read each line and process it */
      protected void parseOutput() throws IOException, SAXException {
          BufferedReader br = new BufferedReader(new InputStreamReader(metricsOutput));
          String line = null;
          while ( (line = br.readLine()) != null ){
              processLine(line);
          }
      }
  
      /**
       * Process a metrics line. If the metrics is invalid and that this is not
       * the header line, it is display as info.
       * @param line the line to process, it is normally a line full of metrics.
       */
      protected void processLine(String line) throws SAXException {
          if ( line.startsWith("Construct\tV(G)\tLOC\tDIT\tNOA\tNRM\tNLM\tWMC\tRFC\tDAC\tFANOUT\tCBO\tLCOM\tNOCL") ){
              return;
          }
          try {
              MetricsElement elem = MetricsElement.parse(line);
              startElement(elem);
          } catch (ParseException e) {
          	e.printStackTrace();
              // invalid lines are sent to the output as information, it might be anything,
             task.log(line, Project.MSG_INFO);
          }
      }
      
      /**
       * Start a new construct. Elements are popped until we are on the same
       * parent node, then the element type is guessed and pushed on the
       * stack.
       * @param elem the element to process.
       * @throws SAXException thrown if there is a problem when sending SAX events.
       */
      protected void startElement(MetricsElement elem) throws SAXException {
          // if there are elements in the stack we possibly need to close one or
          // more elements previous to this one until we got its parent
          int indent = elem.getIndent();
          if ( stack.size() > 0 ){
              ElementEntry previous = (ElementEntry)stack.peek();
              // close nodes until you got the parent.
              try {
                  while ( indent <= previous.getIndent() && stack.size() > 0){
                      stack.pop();
                      metricsHandler.endElement("", previous.getType(), previous.getType());
                          previous = (ElementEntry)stack.peek();
                  }
              } catch (EmptyStackException ignored){}
          }
          
          // ok, now start the new construct
          String type = getConstructType(elem);
          Attributes attrs = createAttributes(elem);
          metricsHandler.startElement("", type, type, attrs);
  
          // make sure we keep track of what we did, that's history
          stack.push( new ElementEntry(type, indent) );
      }
  
      /**
       * return the construct type of the element. We can hardly recognize the
       * type of a metrics element, so we are kind of forced to do some black
       * magic based on the name and indentation to recognize the type.
       * @param elem  the metrics element to guess for its type.
       * @return the type of the metrics element, either PACKAGE, FILE, CLASS or
       * METHOD.
       */
      protected String getConstructType(MetricsElement elem){
          // ok no doubt, it's a file
          if ( elem.isCompilationUnit() ){
              return FILE;
          }
          
          // same, we're sure it's a method
          if ( elem.isMethod() ){
              return METHOD;
          }
  
          // if it's empty, and none of the above it should be a package
          if ( stack.size() == 0 ){
              return PACKAGE;
          }
          
          // ok, this is now black magic time, we will guess the type based on
          // the previous type and its indent...
          final ElementEntry previous = (ElementEntry)stack.peek();
          final String prevType = previous.getType();
          final int prevIndent = previous.getIndent();
          final int indent = elem.getIndent();
          // we're just under a file with a bigger indent so it's a class
          if ( prevType.equals(FILE) && indent > prevIndent ){
              return CLASS;
          }
  
          // we're just under a class with a greater or equals indent, it's a class
          // (there might be several classes in a compilation unit and inner classes as well)
          if ( prevType.equals(CLASS) && indent >= prevIndent ){
              return CLASS;
          }
          
          // we assume the other are package
          return PACKAGE;
      }    
      
      
      /**
       * Create all attributes of a MetricsElement skipping those who have an
       * empty string
       * @param   elem    
       */
      protected Attributes createAttributes(MetricsElement elem){
          AttributesImpl impl = new AttributesImpl();
          int i = 0;
          String name = ATTRIBUTES[i++];
          impl.addAttribute("", name, name, "CDATA", elem.getName());
          Enumeration metrics = elem.getMetrics();
          for (; metrics.hasMoreElements(); i++){
              String value = (String)metrics.nextElement();
              if ( value.length() > 0 ){
                  name = ATTRIBUTES[i];
                  impl.addAttribute("", name, name, "CDATA", value);
              }
          }
          return impl;
      }
      
      /**
       * helper class to keep track of elements via its type and indent
       * that's all we need to guess a type.
       */
      private final static class ElementEntry {
          private String type;
          private int indent;
          ElementEntry(String type, int indent){
              this.type = type;
              this.indent = indent;
          }
          public String getType(){
              return type;
          }
          public int getIndent() {
              return indent;
          }
      }
  }
  
  class MetricsElement {
  
  	private final static NumberFormat METAMATA_NF;
  	
  	private final static NumberFormat NEUTRAL_NF;
  	static {
          METAMATA_NF = NumberFormat.getInstance();
          METAMATA_NF.setMaximumFractionDigits(1);
          NEUTRAL_NF = NumberFormat.getInstance();
  		if (NEUTRAL_NF instanceof DecimalFormat) {
  			((DecimalFormat) NEUTRAL_NF).applyPattern("###0.###;-###0.###");
  		}
          NEUTRAL_NF.setMaximumFractionDigits(1);
  	}
      
      private int indent;
      
      private String construct;
      
      private Vector metrics;
      
      MetricsElement(int indent, String construct, Vector metrics){
          this.indent = indent;
          this.construct = construct;
          this.metrics = metrics;
      }
      
      public int getIndent(){
          return indent;
      }
      
      public String getName(){
          return construct;
      }
  
      public Enumeration getMetrics(){
          return metrics.elements();
      }
      
      public boolean isCompilationUnit(){
          return ( construct.endsWith(".java") || construct.endsWith(".class") );
      }
          
      public boolean isMethod(){
          return ( construct.endsWith("(...)") || construct.endsWith("()") );
      }
      
      public static MetricsElement parse(String line) throws ParseException {
          final Vector metrics = new Vector();
          int pos;
          
          // i'm using indexOf since I need to know if there are empty strings
          // between tabs and I find it easier than with StringTokenizer
          while ( (pos = line.indexOf('\t')) != -1 ){
              String token = line.substring(0, pos);
              // only parse what coudl be a valid number. ie not constructs nor no value
              /*if (metrics.size() != 0 || token.length() != 0){
  	            Number num = METAMATA_NF.parse(token); // parse with Metamata NF
  	            token = NEUTRAL_NF.format(num.doubleValue()); // and format with a neutral NF
  	        }*/
              metrics.addElement( token );
              line = line.substring(pos + 1);
          }
          metrics.addElement( line );
          
          // there should be exactly 14 tokens (1 name + 13 metrics), if not, there is a problem !
          if ( metrics.size() != 14 ){
              throw new ParseException("Could not parse the following line as a metrics: -->" + line +"<--", -1);
          }
          
          // remove the first token it's made of the indentation string and the
          // construct name, we'll need all this to figure out what type of
          // construct it is since we lost all semantics :(
          // (#indent[/]*)(#construct.*)
          String name = (String)metrics.remove(0);
          int indent = 0;
          pos = name.lastIndexOf('/');
          if (pos != -1){
              name = name.substring(pos + 1);
              indent = pos + 1; // indentation is last position of token + 1
          }
          return new MetricsElement(indent, name, metrics);
      }
  }
  
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java
  
  Index: MMetrics.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2000 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  package org.apache.tools.ant.taskdefs.optional.metamata;
  
  import org.apache.tools.ant.BuildException;
  import org.apache.tools.ant.Project;
  import org.apache.tools.ant.Task;
  import org.apache.tools.ant.taskdefs.*;
  import org.apache.tools.ant.types.*;
  
  
  
  import java.io.*;
  import java.util.*;
  
  /**
   * Calculates global complexity and quality metrics on Java source code.
   *
   * You will not be able to use this task with the evaluation version since
   * as of Metamata 2.0, Metrics does not support command line :-(
   *
   * For more information, visit the website at
   * <a href="http://www.metamata.com">www.metamata.com</a>
   *
   * @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
   */
  public class MMetrics extends AbstractMetamataTask {
  /*
      The command line options as of Metamata 2.0 are as follows:
  
  Usage
      mmetrics <option>... <path>...
  
  Parameters
      path              File or directory to measure.
  
  Options
      -arguments   -A   <file>      Includes command line arguments from file.
      -classpath   -cp  <path>      Sets class path (also source path unless one
                                    explicitly set). Overrides METAPATH/CLASSPATH.
      -compilation-units            Measure compilation units.
      -files                        Measure compilation units.
      -format      -f   <format>    Sets output format, default output file type.
      -help        -h               Prints help and exits.
      -indent      -i   <string>    Sets string used to indent labels one level.
      -methods                      Measure methods, types, and compilation units.
      -output      -o   <file>      Sets output file name.
      -quiet       -q               Suppresses copyright message.
      -sourcepath       <path>      Sets source path. Overrides SOURCEPATH.
      -types                        Measure types and compilation units.
      -verbose     -v               Prints all messages.
      -version     -V               Prints version and exits.
  
  Format Options
      comma csv                     Format output as comma-separated text.
      html htm                      Format output as an HTML table.
      tab tab-separated tsv         Format output as tab-separated text.
      text txt                      Format output as space-aligned text.
  */
  
      /** the granularity mode. Should be one of 'files', 'methods' and 'types'. */
      protected String granularity = null;
  
      /** the XML output file */
      protected File outFile = null;
      
      /** the location of the temporary txt report */
      protected File tmpFile = createTmpFile();
  
      protected Path path = null;
  
      //--------------------------- PUBLIC METHODS -------------------------------
  
      /** default constructor */
      public MMetrics() {
          super("com.metamata.sc.MMetrics");
      }
  
      /**
       * set the granularity of the audit. Should be one of 'files', 'methods'
       * or 'types'.
       * @param granularity   the audit reporting mode.
       */
      public void setGranularity(String granularity){
          this.granularity = granularity;
      }
  
      /**
       * Set the output XML file
       * @param file the xml file to write the XML report to.
       */
      public void setTofile(File file){
          this.outFile = file;
      }
  
      /**
       * Set a new path (directory) to measure metrics from.
       * @return the path instance to use.
       */
      public Path createPath(){
          if (path == null) {
              path = new Path(project);
          }
          return path;
  
      }
  
      //------------------- PROTECTED / PRIVATE METHODS --------------------------
  
  
      // check for existing options and outfile, all other are optional
      protected void checkOptions() throws BuildException {
          super.checkOptions();
  
          if ( !"files".equals(granularity) && !"methods".equals(granularity)
             && !"types".equals(granularity) ){
              throw new BuildException("Metrics reporting granularity is invalid. Must be one of 'files', 'methods', 'types'");
          }
          if (outFile == null){
              throw new BuildException("Output XML file must be set via 'tofile' attribute.");
          }
          if (path == null && fileSets.size() == 0){
              throw new BuildException("Must set either paths (path element) or files (fileset element)");
          }
          // I don't accept dirs and files at the same time, I cannot recognize the semantic in the result
          if (path != null && fileSets.size() > 0){
              throw new BuildException("Cannot set paths (path element) and files (fileset element) at the same time");
          }
      }
  
      protected void execute0(ExecuteStreamHandler handler) throws BuildException {
          super.execute0(handler);
          transformFile();
      }
      
      /**
       * transform the generated file via the handler
       * This function can either be called if the result is written to the output
       * file via -output or we could use the handler directly on stdout if not.
       * @see #createStreamHandler()
       */
      protected void transformFile() throws BuildException {
          FileInputStream tmpStream = null;
          try {
              tmpStream = new FileInputStream( tmpFile );
          } catch (IOException e){
              throw new BuildException("Error reading temporary file: " + tmpFile, e);
          }
          FileOutputStream xmlStream = null;
          try {
              xmlStream = new FileOutputStream(outFile);
              ExecuteStreamHandler xmlHandler = new MMetricsStreamHandler(this, xmlStream);
              xmlHandler.setProcessOutputStream(tmpStream);
              xmlHandler.start();
              xmlHandler.stop();
          } catch (IOException e){
              throw new BuildException("Error creating output file: " + outFile, e);
          } finally {
              if (xmlStream != null){
                  try {
                      xmlStream.close();
                  } catch (IOException ignored){}
              }
              if (tmpStream != null){
                  try {
                      tmpStream.close();
                  } catch (IOException ignored){}
              }
          }
      }
      
  
      /** cleanup the temporary txt report */
      protected void cleanUp() throws BuildException {
          try {
              super.cleanUp();
          } finally {
              if (tmpFile != null){
                  tmpFile.delete();
                  tmpFile = null;
              }
          }
      }
  
      /**
       * if the report is transform via a temporary txt file we should use a
       * a normal logger here, otherwise we could use the metrics handler
       * directly to capture and transform the output on stdout to XML.
       */
      protected ExecuteStreamHandler createStreamHandler(){
          // write the report directtly to an XML stream
          // return new MMetricsStreamHandler(this, xmlStream);
          return new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_INFO);
      }
  
  
      protected Vector getOptions(){
          Vector options = new Vector(512);
          // there is a bug in Metamata 2.0 build 37. The sourcepath argument does
          // not work. So we will use the sourcepath prepended to classpath. (order
          // is important since Metamata looks at .class and .java)
          if (sourcePath != null){
              sourcePath.append(classPath); // srcpath is prepended
              classPath = sourcePath;
              sourcePath = null; // prevent from using -sourcepath
          }
                  
          // don't forget to modify the pattern if you change the options reporting
          if (classPath != null){
              options.addElement("-classpath");
              options.addElement(classPath);
          }
          options.addElement( "-output" );
          options.addElement( tmpFile.toString() );
          
          options.addElement( "-" + granularity);
          
          // display the metamata copyright
          // options.addElement( "-quiet");
          options.addElement( "-format");
          
          // need this because that's what the handler is using, it's
          // way easier to process than any other separator
          options.addElement( "tab");
          
          // specify a / as the indent character, used by the handler.
          options.addElement( "-i");
          options.addElement( "/");
          
          // directories
          String[] dirs = path.list();
          for (int i = 0; i < dirs.length; i++){
              options.addElement( dirs[i] );
          }
          // files next. 
          addAllVector(options, includedFiles.keys());
          return options;
      }
  
  }
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java
  
  Index: MAuditStreamHandler.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2000 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Ant", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  package org.apache.tools.ant.taskdefs.optional.metamata;
  
  import org.apache.tools.ant.Project;
  import org.apache.tools.ant.Task;
  import org.apache.tools.ant.taskdefs.*;
  import org.apache.tools.ant.types.*;
  import org.apache.tools.ant.util.regexp.*;
  import org.apache.tools.ant.BuildException;
  import org.apache.tools.ant.util.DOMElementWriter;
  
  import org.w3c.dom.*;
  
  import java.io.*;
  import java.util.*;
  import javax.xml.parsers.*;
  
  
  /**
   * This is a very bad stream handler for the MAudit task.
   * All report to stdout that does not match a specific report pattern is dumped
   * to the Ant output as warn level. The report that match the pattern is stored
   * in a map with the key being the filepath that caused the error report.
   * <p>
   * The limitation with the choosen implementation is clear:
   * <ul>
   * <li>it does not handle multiline report( message that has \n ). the part until
   * the \n will be stored and the other part (which will not match the pattern)
   * will go to Ant output in Warn level.
   * <li>it does not report error that goes to stderr.
   * </ul>
   *
   * @author <a href="sbailliez@imediation.com">Stephane Bailliez</a>
   */
  class MAuditStreamHandler implements ExecuteStreamHandler {
  
      protected MAudit task;
  
      /** reader for stdout */
      protected BufferedReader br;
  
      /** matcher that will be used to extract the info from the line */
      protected RegexpMatcher matcher;
  
      /**
       * this is where the XML output will go, should mostly be a file
       * the caller is responsible for flushing and closing this stream
       */
      protected OutputStream xmlOut = null;
  
      /**
       * the multimap. The key in the map is the filepath that caused the audit
       * error and the value is a vector of MAudit.Violation entries.
       */
      protected Hashtable auditedFiles = new Hashtable();
  
      MAuditStreamHandler(MAudit task, OutputStream xmlOut){
          this.task = task;
          this.xmlOut = xmlOut;
          /** the matcher should be the Oro one. I don't know about the other one */
          matcher = (new RegexpMatcherFactory()).newRegexpMatcher();
          matcher.setPattern(MAudit.AUDIT_PATTERN);
      }
  
      /** Ignore. */
      public void setProcessInputStream(OutputStream os) {}
  
      /** Ignore. */
      public void setProcessErrorStream(InputStream is) {}
  
      /** Set the inputstream */
      public void setProcessOutputStream(InputStream is) throws IOException {
          br = new BufferedReader(new InputStreamReader(is));
      }
  
      /** Invokes parseOutput. This will block until the end :-(*/
      public void start() throws IOException {
          parseOutput(br);
      }
  
      /**
       * Pretty dangerous business here. It serializes what was extracted from
       * the MAudit output and write it to the output.
       */
      public void stop() {
          // serialize the content as XML, move this to another method
          // this is the only code that could be needed to be overrided
          Document doc = getDocumentBuilder().newDocument();
          Element rootElement = doc.createElement("classes");
          Enumeration keys = auditedFiles.keys();
          Hashtable filemapping = task.getFileMapping();
          rootElement.setAttribute("audited", String.valueOf(filemapping.size()));
          rootElement.setAttribute("reported", String.valueOf(auditedFiles.size()));
          int errors = 0;
          while (keys.hasMoreElements()){
              String filepath = (String)keys.nextElement();
              Vector v = (Vector)auditedFiles.get(filepath);
              String fullclassname = (String)filemapping.get(filepath);
              if (fullclassname == null) {
                  task.getProject().log("Could not find class mapping for " + filepath, Project.MSG_WARN);
                  continue;
              }
              int pos = fullclassname.lastIndexOf('.');
              String pkg = (pos == -1) ? "" : fullclassname.substring(0, pos);
              String clazzname = (pos == -1) ? fullclassname : fullclassname.substring(pos + 1);
              Element clazz = doc.createElement("class");
              clazz.setAttribute("package", pkg);
              clazz.setAttribute("name", clazzname);
              clazz.setAttribute("violations", String.valueOf(v.size()));
              errors += v.size();
              for (int i = 0; i < v.size(); i++){
                  MAudit.Violation violation = (MAudit.Violation)v.elementAt(i);
                  Element error = doc.createElement("violation");
                  error.setAttribute("line", String.valueOf(violation.line));
                  error.setAttribute("message", violation.error);
                  clazz.appendChild(error);
              }
              rootElement.appendChild(clazz);
          }
          rootElement.setAttribute("violations", String.valueOf(errors));
  
          // now write it to the outputstream, not very nice code
          if (xmlOut != null) {
              Writer wri = null;
              try {
                  wri = new OutputStreamWriter(xmlOut, "UTF-8");
                  wri.write("<?xml version=\"1.0\"?>\n");
                  (new DOMElementWriter()).write(rootElement, wri, 0, "  ");
                  wri.flush();
              } catch(IOException exc) {
                  task.log("Unable to write log file", Project.MSG_ERR);
              } finally {
                  if (xmlOut != System.out && xmlOut != System.err) {
                      if (wri != null) {
                          try {
                              wri.close();
                          } catch (IOException e) {}
                      }
                  }
              }
          }
  
      }
  
      protected static DocumentBuilder getDocumentBuilder() {
          try {
              return DocumentBuilderFactory.newInstance().newDocumentBuilder();
          }
          catch(Exception exc) {
              throw new ExceptionInInitializerError(exc);
          }
      }
  
      /** read each line and process it */
      protected void parseOutput(BufferedReader br) throws IOException {
          String line = null;
          while ( (line = br.readLine()) != null ){
              processLine(line);
          }
      }
  
      // we suppose here that there is only one report / line.
      // There will obviouslly be a problem if the message is on several lines...
      protected void processLine(String line){
          Vector matches = matcher.getGroups(line);
          if (matches != null) {
              String file = (String)matches.elementAt(1);
              int lineNum = Integer.parseInt((String)matches.elementAt(2));
              String msg = (String)matches.elementAt(3);
              addViolationEntry(file, MAudit.createViolation(lineNum, msg) );
          } else {
              // this doesn't match..report it as info, it could be
              // either the copyright, summary or a multiline message (damn !)
              task.log(line, Project.MSG_INFO);
          }
      }
  
      /** add a violation entry for the file */
      protected void addViolationEntry(String file, MAudit.Violation entry){
              Vector violations = (Vector)auditedFiles.get(file);
              // if there is no decl for this file yet, create it.
              if (violations == null){
                  violations = new Vector();
                  auditedFiles.put(file, violations);
              }
              violations.add( entry );
      }
  
  }
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java
  
  Index: MAudit.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2000 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Ant", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  package org.apache.tools.ant.taskdefs.optional.metamata;
  
  import org.apache.tools.ant.BuildException;
  import org.apache.tools.ant.Project;
  import org.apache.tools.ant.Task;
  import org.apache.tools.ant.taskdefs.*;
  import org.apache.tools.ant.types.*;
  import org.apache.tools.ant.DirectoryScanner;
  import org.apache.tools.ant.util.regexp.*;
  
  import java.io.*;
  import java.util.*;
  
  /**
   * Metamata Audit evaluates Java code for programming errors, weaknesses, and
   * style violation.
   * <p>
   * Metamata Audit exists in three versions:
   * <ul>
   *  <li>The Lite version evaluates about 15 built-in rules.</li>
   *  <li>The Pro version evaluates about 50 built-in rules.</li>
   *  <li>The Enterprise version allows you to add your own customized rules via the API.</li>
   * <ul>
   * For more information, visit the website at
   * <a href="http://www.metamata.com">www.metamata.com</a>
   *
   * @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
   */
  public class MAudit extends AbstractMetamataTask {
  
      /* As of Metamata 2.0, the command line of MAudit is as follows:
      Usage
          maudit <option>... <path>... [-unused <search-path>...]
  
      Parameters
          path               File or directory to audit.
          search-path        File or directory to search for declaration uses.
  
      Options
          -arguments  -A     <file>     Includes command line arguments from file.
          -classpath  -cp    <path>     Sets class path (also source path unless one
                                        explicitly set). Overrides METAPATH/CLASSPATH.
          -exit       -x                Exits after the first error.
          -fix        -f                Automatically fixes certain errors.
          -fullpath                     Prints full path for locations.
          -help       -h                Prints help and exits.
          -list       -l                Creates listing file for each audited file.
          -offsets    -off              Offset and length for locations.
          -output     -o     <file>     Prints output to file.
          -quiet      -q                Suppresses copyright and summary messages.
          -sourcepath        <path>     Sets source path. Overrides SOURCEPATH.
          -tab        -t                Prints a tab character after first argument.
          -unused     -u                Finds declarations unused in search paths.
          -verbose    -v                Prints all messages.
          -version    -V                Prints version and exits.
      */
  
      //---------------------- PUBLIC METHODS ------------------------------------
  
      /** pattern used by maudit to report the error for a file */
      /** RE does not seems to support regexp pattern with comments so i'm stripping it*/
      // (?:file:)?((?#filepath).+):((?#line)\\d+)\\s*:\\s+((?#message).*)
      static final String AUDIT_PATTERN = "(?:file:)?(.+):(\\d+)\\s*:\\s+(.*)";
  
      protected File outFile = null;
      
      protected Path searchPath = null;
      
      protected boolean fix = false;
      
      protected boolean list = false;
      
      protected boolean unused = false;
  
      /** default constructor */
      public MAudit() {
          super("com.metamata.gui.rc.MAudit");
      }
  
      /** set the destination file which should be an xml file */
      public void setTofile(File outFile){
          this.outFile = outFile;
      }
  
      public void setFix(boolean flag){
          this.fix = flag;
      }
  
      public void setList(boolean flag){
          this.list = flag;
      }
  
      public void setUnused(boolean flag){
          this.unused = flag;
      }
  
      public Path createSearchpath(){
          if (searchPath == null){
              searchPath = new Path(project);
          }
          return searchPath;
      }
  
      protected Vector getOptions(){
          Vector options = new Vector(512);
          // there is a bug in Metamata 2.0 build 37. The sourcepath argument does
          // not work. So we will use the sourcepath prepended to classpath. (order
          // is important since Metamata looks at .class and .java)
          if (sourcePath != null){
              sourcePath.append(classPath); // srcpath is prepended
              classPath = sourcePath;
              sourcePath = null; // prevent from using -sourcepath
          }        
          
          // don't forget to modify the pattern if you change the options reporting
          if (classPath != null){
              options.addElement("-classpath");
              options.addElement(classPath.toString());
          }
          // suppress copyright msg when running, we will let it so that this
          // will be the only output to the console if in xml mode
  //      options.addElement("-quiet");
          if (fix){
              options.addElement("-fix");
          }
          options.addElement("-fullpath");
  
          // generate .maudit files much more detailed than the report
          // I don't like it very much, I think it could be interesting
          // to get all .maudit files and include them in the XML.
          if (list){
              options.addElement("-list");
          }
          if (sourcePath != null){
              options.addElement("-sourcepath");
              options.addElement(sourcePath.toString());
          }
          
          if (unused){
              options.addElement("-unused");
              options.addElement(searchPath.toString());
          }
          addAllVector(options, includedFiles.keys());
          return options;
      }
  
      protected void checkOptions() throws BuildException {
          super.checkOptions();
          if (unused && searchPath == null){
              throw new BuildException("'searchpath' element must be set when looking for 'unused' declarations.");
          }
          if (!unused && searchPath != null){
              log("'searchpath' element ignored. 'unused' attribute is disabled.", Project.MSG_WARN);
          }
      }
      
      protected ExecuteStreamHandler createStreamHandler() throws BuildException {
          ExecuteStreamHandler handler = null;
          // if we didn't specify a file, then use a screen report
          if (outFile == null){
             handler = new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_INFO);
          } else {
              try {
                  //XXX
                  OutputStream out = new FileOutputStream( outFile );
                  handler = new MAuditStreamHandler(this, out);
              } catch (IOException e){
                  throw new BuildException(e);
              }
          }
          return handler;
      }
  
      protected void cleanUp() throws BuildException {
          super.cleanUp();
          // at this point if -list is used, we should move
          // the .maudit file since we cannot choose their location :(
          // the .maudit files match the .java files
          // we'll use includedFiles to get the .maudit files.
  
          /*if (out != null){
              // close it if not closed by the handler...
          }*/
      }
  
      /** the inner class used to report violation information */
      static final class Violation {
          int line;
          String error;
      }
  
      /** handy factory to create a violation */
      static final Violation createViolation(int line, String msg){
          Violation violation = new Violation();
          violation.line = line;
          violation.error = msg;
          return violation;
      }
  
  }
  
  
  
  
  
  1.1                  jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java
  
  Index: AbstractMetamataTask.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2000 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  package org.apache.tools.ant.taskdefs.optional.metamata;
  
  import org.apache.tools.ant.BuildException;
  import org.apache.tools.ant.Project;
  import org.apache.tools.ant.Task;
  import org.apache.tools.ant.taskdefs.*;
  import org.apache.tools.ant.types.*;
  import org.apache.tools.ant.DirectoryScanner;
  
  import java.io.*;
  import java.util.*;
  
  /**
   * Somewhat abstract framework to be used for other metama 2.0 tasks.
   * This should include, audit, metrics, cover and mparse.
   *
   * For more information, visit the website at
   * <a href="http://www.metamata.com">www.metamata.com</a>
   *
   * @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
   */
  public abstract class AbstractMetamataTask extends Task{
  
      //--------------------------- ATTRIBUTES -----------------------------------
  
      /**
       * The user classpath to be provided. It matches the -classpath of the
       * command line. The classpath must includes both the <tt>.class</tt> and the
       * <tt>.java</tt> files for accurate audit.
       */
      protected Path classPath = null;
  
      /** the path to the source file */
      protected Path sourcePath = null;
  
      /**
       * Metamata home directory. It will be passed as a <tt>metamata.home</tt> property
       * and should normally matches the environment property <tt>META_HOME</tt>
       * set by the Metamata installer.
       */
      protected File metamataHome = null;
  
      /** the command line used to run MAudit */
      protected CommandlineJava cmdl = new CommandlineJava();
  
      /** the set of files to be audited */
      protected Vector fileSets = new Vector();
  
      /** the options file where are stored the command line options */
      protected File optionsFile = null;
  
      // this is used to keep track of which files were included. It will
      // be set when calling scanFileSets();
      protected Hashtable includedFiles = null;
  
      public AbstractMetamataTask(){
      }
  
      /** initialize the task with the classname of the task to run */
      protected AbstractMetamataTask(String className) {
          cmdl.setVm("java");
          cmdl.setClassname(className);
      }
  
      /** the metamata.home property to run all tasks. */
      public void setMetamatahome(final File metamataHome){
          this.metamataHome = metamataHome;
      }
  
      /** user classpath */
      public Path createClasspath() {
          if (classPath == null) {
              classPath = new Path(project);
          }
          return classPath;
      }
  
      /** create the source path for this task */
      public Path createSourcepath(){
          if (sourcePath == null){
              sourcePath = new Path(project);
          }
          return sourcePath;
      }
  
      /** Creates a nested jvmarg element. */
      public Commandline.Argument createJvmarg() {
          return cmdl.createVmArgument();
      }
  
      /**  -mx or -Xmx depending on VM version */
      public void setMaxmemory(String max){
          if (Project.getJavaVersion().startsWith("1.1")) {
              createJvmarg().setValue("-mx" + max);
          } else {
              createJvmarg().setValue("-Xmx" + max);
          }
      }
  
  
      /** The java files or directory to be audited */
      public void addFileSet(FileSet fs) {
          fileSets.addElement(fs);
      }
  
      /** execute the command line */
      public void execute() throws BuildException {
          try {
              setUp();
              ExecuteStreamHandler handler = createStreamHandler();
              execute0(handler);
          } finally {
              cleanUp();
          }
      }
  
      //--------------------- PRIVATE/PROTECTED METHODS --------------------------
  
      /** check the options and build the command line */
      protected void setUp() throws BuildException {
          checkOptions();
  
          // set the classpath as the jar file
          File jar = getMetamataJar(metamataHome);
          final Path classPath = cmdl.createClasspath(project);
          classPath.createPathElement().setLocation(jar);
  
          // set the metamata.home property
          final Commandline.Argument vmArgs = cmdl.createVmArgument();
          vmArgs.setValue("-Dmetamata.home=" + metamataHome.getAbsolutePath() );
  
          // retrieve all the files we want to scan
          includedFiles = scanFileSets();
          log(includedFiles.size() + " files added for audit", Project.MSG_VERBOSE);
  
          // write all the options to a temp file and use it ro run the process
          Vector options = getOptions();
          optionsFile = createTmpFile();
          generateOptionsFile(optionsFile, options);
          Commandline.Argument args = cmdl.createArgument();
          args.setLine("-arguments " + optionsFile.getAbsolutePath());
      }
  
      /**
       * create a stream handler that will be used to get the output since
       * metamata tools do not report with convenient files such as XML.
       */
      protected abstract ExecuteStreamHandler createStreamHandler();
  
  
      /** execute the process with a specific handler */
      protected void execute0(ExecuteStreamHandler handler) throws BuildException {
          final Execute process = new Execute(handler);
          log(cmdl.toString(), Project.MSG_VERBOSE);
          process.setCommandline(cmdl.getCommandline());
          try {
              if (process.execute() != 0) {
                  throw new BuildException("Metamata task failed.");
              }
          } catch (IOException e){
              throw new BuildException("Failed to launch Metamata task: " + e);
          }
      }
  
      /** clean up all the mess that we did with temporary objects */
      protected void cleanUp(){
          if (optionsFile != null){
              optionsFile.delete();
              optionsFile = null;
          }
      }
  
      /** return the location of the jar file used to run */
      protected final File getMetamataJar(File home){
          return new File(home.getAbsoluteFile(), "lib/metamata.jar");
      }
  
      /** validate options set */
      protected void checkOptions() throws BuildException {
          // do some validation first
          if (metamataHome == null || !metamataHome.exists()){
              throw new BuildException("'metamatahome' must point to Metamata home directory.");
          }
          metamataHome = project.resolveFile(metamataHome.getPath());
          File jar = getMetamataJar(metamataHome);
          if (!jar.exists()){
              throw new BuildException( jar + " does not exist. Check your metamata installation.");
          }
      }
  
      /** return all options of the command line as string elements */
      protected abstract Vector getOptions();
  
  
      protected void generateOptionsFile(File tofile, Vector options) throws BuildException {
          FileWriter fw = null;
          try {
              fw = new FileWriter(tofile);
              PrintWriter pw = new PrintWriter(fw);
              final int size = options.size();
              for (int i = 0; i < size; i++){
                  pw.println( options.elementAt(i) );
              }
              pw.flush();
          } catch (IOException e){
              throw new BuildException("Error while writing options file " + tofile, e);
          } finally {
              if (fw != null){
                  try {
                      fw.close();
                  } catch (IOException ignored){}
              }
          }
      }
  
  
      protected Hashtable getFileMapping(){
          return includedFiles;
      }
      /**
       * convenient method for JDK 1.1. Will copy all elements from src to dest
       */
      protected static final void addAllVector(Vector dest, Enumeration files){
          while (files.hasMoreElements()) {
              dest.addElement( files.nextElement() );
          }
      }
      
      protected final static File createTmpFile(){
          // must be compatible with JDK 1.1 !!!!
          final long rand = (new Random(System.currentTimeMillis())).nextLong();
          File file = new File("metamata" + rand + ".tmp");
          return file;
      }
  
      /**
       * @return the list of .java files (as their absolute path) that should
       *         be audited.
       */
      protected Hashtable scanFileSets(){
          Hashtable files = new Hashtable();
          for (int i = 0; i < fileSets.size(); i++){
              FileSet fs = (FileSet) fileSets.elementAt(i);
              DirectoryScanner ds = fs.getDirectoryScanner(project);
              ds.scan();
              String[] f = ds.getIncludedFiles();
              log(i + ") Adding " + f.length + " files from directory " + ds.getBasedir(), Project.MSG_VERBOSE);
              for (int j = 0; j < f.length; j++){
                  String pathname = f[j];
                  if ( pathname.endsWith(".java") ){
                      File file = new File( ds.getBasedir(), pathname);
  //                  file = project.resolveFile(file.getAbsolutePath());
                      String classname = pathname.substring(0, pathname.length()-".java".length());
                      classname = classname.replace(File.separatorChar, '.');
                     files.put( file.getAbsolutePath(), classname ); // it's a java file, add it.
                  }
              }
          }
          return files;
      }
  
  }