Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 446   Methods: 21
NCLOC: 236   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
MMetricsStreamHandler.java 0% 0% 0% 0%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2001-2002 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 package org.apache.tools.ant.taskdefs.optional.metamata;
 55   
 
 56   
 
 57   
 import java.io.BufferedReader;
 58   
 import java.io.IOException;
 59   
 import java.io.InputStream;
 60   
 import java.io.InputStreamReader;
 61   
 import java.io.OutputStream;
 62   
 import java.io.OutputStreamWriter;
 63   
 import java.text.DecimalFormat;
 64   
 import java.text.NumberFormat;
 65   
 import java.text.ParseException;
 66   
 import java.util.Date;
 67   
 import java.util.EmptyStackException;
 68   
 import java.util.Enumeration;
 69   
 import java.util.Stack;
 70   
 import java.util.Vector;
 71   
 import javax.xml.transform.OutputKeys;
 72   
 import javax.xml.transform.Transformer;
 73   
 import javax.xml.transform.TransformerFactory;
 74   
 import javax.xml.transform.sax.SAXTransformerFactory;
 75   
 import javax.xml.transform.sax.TransformerHandler;
 76   
 import javax.xml.transform.stream.StreamResult;
 77   
 import org.apache.tools.ant.BuildException;
 78   
 import org.apache.tools.ant.Project;
 79   
 import org.apache.tools.ant.Task;
 80   
 import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
 81   
 import org.apache.tools.ant.util.DateUtils;
 82   
 import org.xml.sax.Attributes;
 83   
 import org.xml.sax.SAXException;
 84   
 import org.xml.sax.helpers.AttributesImpl;
 85   
 
 86   
 /**
 87   
  * A handy metrics handler. Most of this code was done only with the
 88   
  * screenshots on the documentation since the evaluation version as
 89   
  * of this writing does not allow to save metrics or to run it via
 90   
  * command line.
 91   
  * <p>
 92   
  * This class can be used to transform a text file or to process the
 93   
  * output stream directly.
 94   
  *
 95   
  * @author  <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
 96   
  */
 97   
 public class MMetricsStreamHandler implements ExecuteStreamHandler {
 98   
 
 99   
     /** CLASS construct, it should be named something like 'MyClass' */
 100   
     private static final String CLASS = "class";
 101   
 
 102   
     /** package construct, it should be look like 'com.mycompany.something' */
 103   
     private static final String PACKAGE = "package";
 104   
 
 105   
     /** FILE construct, it should look like something 'MyClass.java' or 'MyClass.class' */
 106   
     private static final String FILE = "file";
 107   
 
 108   
     /** METHOD construct, it should looke like something 'doSomething(...)' or 'doSomething()' */
 109   
     private static final String METHOD = "method";
 110   
 
 111   
     private static final String[] ATTRIBUTES = {
 112   
         "name", "vg", "loc", "dit", "noa", "nrm", "nlm", "wmc",
 113   
         "rfc", "dac", "fanout", "cbo", "lcom", "nocl"};
 114   
 
 115   
     /** reader for stdout */
 116   
     private InputStream metricsOutput;
 117   
 
 118   
     /**
 119   
      * this is where the XML output will go, should mostly be a file
 120   
      * the caller is responsible for flushing and closing this stream
 121   
      */
 122   
     private OutputStream xmlOutputStream;
 123   
 
 124   
     /** metrics handler */
 125   
     private TransformerHandler metricsHandler;
 126   
 
 127   
     /** the task */
 128   
     private Task task;
 129   
 
 130   
     /**
 131   
      * the stack where are stored the metrics element so that they we can
 132   
      * know if we have to close an element or not.
 133   
      */
 134   
     private Stack stack = new Stack();
 135   
 
 136   
     /** initialize this handler */
 137  0
     MMetricsStreamHandler(Task task, OutputStream xmlOut) {
 138  0
         this.task = task;
 139  0
         this.xmlOutputStream = xmlOut;
 140   
     }
 141   
 
 142   
     /** Ignore. */
 143  0
     public void setProcessInputStream(OutputStream p1) throws IOException {
 144   
     }
 145   
 
 146   
     /** Ignore. */
 147  0
     public void setProcessErrorStream(InputStream p1) throws IOException {
 148   
     }
 149   
 
 150   
     /** Set the inputstream */
 151  0
     public void setProcessOutputStream(InputStream is) throws IOException {
 152  0
         metricsOutput = is;
 153   
     }
 154   
 
 155  0
     public void start() throws IOException {
 156   
         // create the transformer handler that will be used to serialize
 157   
         // the output.
 158  0
         TransformerFactory factory = TransformerFactory.newInstance();
 159  0
         if (!factory.getFeature(SAXTransformerFactory.FEATURE)) {
 160  0
             throw new IllegalStateException("Invalid Transformer factory feature");
 161   
         }
 162  0
         try {
 163  0
             metricsHandler = ((SAXTransformerFactory) factory).newTransformerHandler();
 164  0
             metricsHandler.setResult(new StreamResult(new OutputStreamWriter(xmlOutputStream, "UTF-8")));
 165  0
             Transformer transformer = metricsHandler.getTransformer();
 166  0
             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
 167   
 
 168   
             // start the document with a 'metrics' root
 169  0
             final Date now = new Date();
 170  0
             metricsHandler.startDocument();
 171  0
             AttributesImpl attr = new AttributesImpl();
 172  0
             attr.addAttribute("", "company", "company", "CDATA", "metamata");
 173  0
             attr.addAttribute("", "snapshot_created", "snapshot_created", "CDATA",
 174   
                     DateUtils.format(now, DateUtils.ISO8601_DATETIME_PATTERN));
 175   
 //            attr.addAttribute("", "elapsed_time", "elapsed_time", "CDATA", String.valueOf(now.getTime() - program_start.getTime()));
 176  0
             attr.addAttribute("", "program_start", "program_start", "CDATA",
 177   
                     DateUtils.format(new Date(), DateUtils.ISO8601_DATETIME_PATTERN));
 178  0
             metricsHandler.startElement("", "metrics", "metrics", attr);
 179   
 
 180   
             // now parse the whole thing
 181  0
             parseOutput();
 182   
 
 183   
         } catch (Exception e) {
 184  0
             throw new BuildException(e);
 185   
         }
 186   
     }
 187   
 
 188   
     /**
 189   
      * Pretty dangerous business here.
 190   
      */
 191  0
     public void stop() {
 192  0
         try {
 193   
             // we need to pop everything and close elements that have not been
 194   
             // closed yet.
 195  0
             while (stack.size() > 0) {
 196  0
                 ElementEntry elem = (ElementEntry) stack.pop();
 197  0
                 metricsHandler.endElement("", elem.getType(), elem.getType());
 198   
             }
 199   
             // close the root
 200  0
             metricsHandler.endElement("", "metrics", "metrics");
 201   
             // document is finished for good
 202  0
             metricsHandler.endDocument();
 203   
         } catch (SAXException e) {
 204  0
             e.printStackTrace();
 205  0
             throw new IllegalStateException(e.getMessage());
 206   
         }
 207   
     }
 208   
 
 209   
     /** read each line and process it */
 210  0
     protected void parseOutput() throws IOException, SAXException {
 211  0
         BufferedReader br = new BufferedReader(new InputStreamReader(metricsOutput));
 212  0
         String line = null;
 213  0
         while ((line = br.readLine()) != null) {
 214  0
             processLine(line);
 215   
         }
 216   
     }
 217   
 
 218   
     /**
 219   
      * Process a metrics line. If the metrics is invalid and that this is not
 220   
      * the header line, it is display as info.
 221   
      * @param line the line to process, it is normally a line full of metrics.
 222   
      */
 223  0
     protected void processLine(String line) throws SAXException {
 224  0
         if (line.startsWith("Construct\tV(G)\tLOC\tDIT\tNOA\tNRM\tNLM\tWMC\tRFC\tDAC\tFANOUT\tCBO\tLCOM\tNOCL")) {
 225  0
             return;
 226   
         }
 227  0
         try {
 228  0
             MetricsElement elem = MetricsElement.parse(line);
 229  0
             startElement(elem);
 230   
         } catch (ParseException e) {
 231   
             //e.printStackTrace();
 232   
             // invalid lines are sent to the output as information, it might be anything,
 233  0
             task.log(line, Project.MSG_INFO);
 234   
         }
 235   
     }
 236   
 
 237   
     /**
 238   
      * Start a new construct. Elements are popped until we are on the same
 239   
      * parent node, then the element type is guessed and pushed on the
 240   
      * stack.
 241   
      * @param elem the element to process.
 242   
      * @throws SAXException thrown if there is a problem when sending SAX events.
 243   
      */
 244  0
     protected void startElement(MetricsElement elem) throws SAXException {
 245   
         // if there are elements in the stack we possibly need to close one or
 246   
         // more elements previous to this one until we got its parent
 247  0
         int indent = elem.getIndent();
 248  0
         if (stack.size() > 0) {
 249  0
             ElementEntry previous = (ElementEntry) stack.peek();
 250   
             // close nodes until you got the parent.
 251  0
             try {
 252  0
                 while (indent <= previous.getIndent() && stack.size() > 0) {
 253  0
                     stack.pop();
 254  0
                     metricsHandler.endElement("", previous.getType(), previous.getType());
 255  0
                     previous = (ElementEntry) stack.peek();
 256   
                 }
 257   
             } catch (EmptyStackException ignored) {
 258   
             }
 259   
         }
 260   
 
 261   
         // ok, now start the new construct
 262  0
         String type = getConstructType(elem);
 263  0
         Attributes attrs = createAttributes(elem);
 264  0
         metricsHandler.startElement("", type, type, attrs);
 265   
 
 266   
         // make sure we keep track of what we did, that's history
 267  0
         stack.push(new ElementEntry(type, indent));
 268   
     }
 269   
 
 270   
     /**
 271   
      * return the construct type of the element. We can hardly recognize the
 272   
      * type of a metrics element, so we are kind of forced to do some black
 273   
      * magic based on the name and indentation to recognize the type.
 274   
      * @param elem  the metrics element to guess for its type.
 275   
      * @return the type of the metrics element, either PACKAGE, FILE, CLASS or
 276   
      * METHOD.
 277   
      */
 278  0
     protected String getConstructType(MetricsElement elem) {
 279   
         // ok no doubt, it's a file
 280  0
         if (elem.isCompilationUnit()) {
 281  0
             return FILE;
 282   
         }
 283   
 
 284   
         // same, we're sure it's a method
 285  0
         if (elem.isMethod()) {
 286  0
             return METHOD;
 287   
         }
 288   
 
 289   
         // if it's empty, and none of the above it should be a package
 290  0
         if (stack.size() == 0) {
 291  0
             return PACKAGE;
 292   
         }
 293   
 
 294   
         // ok, this is now black magic time, we will guess the type based on
 295   
         // the previous type and its indent...
 296  0
         final ElementEntry previous = (ElementEntry) stack.peek();
 297  0
         final String prevType = previous.getType();
 298  0
         final int prevIndent = previous.getIndent();
 299  0
         final int indent = elem.getIndent();
 300   
         // we're just under a file with a bigger indent so it's a class
 301  0
         if (prevType.equals(FILE) && indent > prevIndent) {
 302  0
             return CLASS;
 303   
         }
 304   
 
 305   
         // we're just under a class with a greater or equals indent, it's a class
 306   
         // (there might be several classes in a compilation unit and inner classes as well)
 307  0
         if (prevType.equals(CLASS) && indent >= prevIndent) {
 308  0
             return CLASS;
 309   
         }
 310   
 
 311   
         // we assume the other are package
 312  0
         return PACKAGE;
 313   
     }
 314   
 
 315   
 
 316   
     /**
 317   
      * Create all attributes of a MetricsElement skipping those who have an
 318   
      * empty string
 319   
      */
 320  0
     protected Attributes createAttributes(MetricsElement elem) {
 321  0
         AttributesImpl impl = new AttributesImpl();
 322  0
         int i = 0;
 323  0
         String name = ATTRIBUTES[i++];
 324  0
         impl.addAttribute("", name, name, "CDATA", elem.getName());
 325  0
         Enumeration metrics = elem.getMetrics();
 326  0
         for (; metrics.hasMoreElements(); i++) {
 327  0
             String value = (String) metrics.nextElement();
 328  0
             if (value.length() > 0) {
 329  0
                 name = ATTRIBUTES[i];
 330  0
                 impl.addAttribute("", name, name, "CDATA", value);
 331   
             }
 332   
         }
 333  0
         return impl;
 334   
     }
 335   
 
 336   
     /**
 337   
      * helper class to keep track of elements via its type and indent
 338   
      * that's all we need to guess a type.
 339   
      */
 340   
     private static final class ElementEntry {
 341   
         private String type;
 342   
         private int indent;
 343   
 
 344  0
         ElementEntry(String type, int indent) {
 345  0
             this.type = type;
 346  0
             this.indent = indent;
 347   
         }
 348   
 
 349  0
         public String getType() {
 350  0
             return type;
 351   
         }
 352   
 
 353  0
         public int getIndent() {
 354  0
             return indent;
 355   
         }
 356   
     }
 357   
 }
 358   
 
 359   
 class MetricsElement {
 360   
 
 361   
     private static final NumberFormat METAMATA_NF;
 362   
 
 363   
     private static final NumberFormat NEUTRAL_NF;
 364   
 
 365   
     static {
 366  0
         METAMATA_NF = NumberFormat.getInstance();
 367  0
         METAMATA_NF.setMaximumFractionDigits(1);
 368  0
         NEUTRAL_NF = NumberFormat.getInstance();
 369  0
         if (NEUTRAL_NF instanceof DecimalFormat) {
 370  0
             ((DecimalFormat) NEUTRAL_NF).applyPattern("###0.###;-###0.###");
 371   
         }
 372  0
         NEUTRAL_NF.setMaximumFractionDigits(1);
 373   
     }
 374   
 
 375   
     private int indent;
 376   
 
 377   
     private String construct;
 378   
 
 379   
     private Vector metrics;
 380   
 
 381  0
     MetricsElement(int indent, String construct, Vector metrics) {
 382  0
         this.indent = indent;
 383  0
         this.construct = construct;
 384  0
         this.metrics = metrics;
 385   
     }
 386   
 
 387  0
     public int getIndent() {
 388  0
         return indent;
 389   
     }
 390   
 
 391  0
     public String getName() {
 392  0
         return construct;
 393   
     }
 394   
 
 395  0
     public Enumeration getMetrics() {
 396  0
         return metrics.elements();
 397   
     }
 398   
 
 399  0
     public boolean isCompilationUnit() {
 400  0
         return (construct.endsWith(".java") || construct.endsWith(".class"));
 401   
     }
 402   
 
 403  0
     public boolean isMethod() {
 404  0
         return (construct.endsWith("(...)") || construct.endsWith("()"));
 405   
     }
 406   
 
 407  0
     public static MetricsElement parse(String line) throws ParseException {
 408  0
         final Vector metrics = new Vector();
 409  0
         int pos;
 410   
 
 411   
         // i'm using indexOf since I need to know if there are empty strings
 412   
         // between tabs and I find it easier than with StringTokenizer
 413  0
         while ((pos = line.indexOf('\t')) != -1) {
 414  0
             String token = line.substring(0, pos);
 415   
             // only parse what coudl be a valid number. ie not constructs nor no value
 416   
             /*if (metrics.size() != 0 || token.length() != 0){
 417   
                 Number num = METAMATA_NF.parse(token); // parse with Metamata NF
 418   
                 token = NEUTRAL_NF.format(num.doubleValue()); // and format with a neutral NF
 419   
             }*/
 420  0
             metrics.addElement(token);
 421  0
             line = line.substring(pos + 1);
 422   
         }
 423  0
         metrics.addElement(line);
 424   
 
 425   
         // there should be exactly 14 tokens (1 name + 13 metrics), if not, there is a problem !
 426  0
         if (metrics.size() != 14) {
 427  0
             throw new ParseException("Could not parse the following line as a metrics: -->" + line + "<--", -1);
 428   
         }
 429   
 
 430   
         // remove the first token it's made of the indentation string and the
 431   
         // construct name, we'll need all this to figure out what type of
 432   
         // construct it is since we lost all semantics :(
 433   
         // (#indent[/]*)(#construct.*)
 434  0
         String name = (String) metrics.elementAt(0);
 435  0
         metrics.removeElementAt(0);
 436  0
         int indent = 0;
 437  0
         pos = name.lastIndexOf('/');
 438  0
         if (pos != -1) {
 439  0
             name = name.substring(pos + 1);
 440  0
             indent = pos + 1; // indentation is last position of token + 1
 441   
         }
 442  0
         return new MetricsElement(indent, name, metrics);
 443   
     }
 444   
 }
 445   
 
 446