|
|
@@ -0,0 +1,419 @@ |
|
|
|
/* |
|
|
|
* 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); |
|
|
|
} |
|
|
|
} |
|
|
|
|