- /*
- * Copyright 2001-2002,2004 The Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
- package org.apache.tools.ant.taskdefs.optional.metamata;
-
-
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.io.OutputStreamWriter;
- import java.text.DecimalFormat;
- import java.text.NumberFormat;
- import java.text.ParseException;
- import java.util.Date;
- import java.util.EmptyStackException;
- import java.util.Enumeration;
- import java.util.Stack;
- import java.util.Vector;
- import javax.xml.transform.OutputKeys;
- import javax.xml.transform.Transformer;
- import javax.xml.transform.TransformerFactory;
- import javax.xml.transform.sax.SAXTransformerFactory;
- import javax.xml.transform.sax.TransformerHandler;
- import javax.xml.transform.stream.StreamResult;
- import org.apache.tools.ant.BuildException;
- import org.apache.tools.ant.Project;
- import org.apache.tools.ant.Task;
- import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
- import org.apache.tools.ant.util.DateUtils;
- import org.xml.sax.Attributes;
- import org.xml.sax.SAXException;
- import org.xml.sax.helpers.AttributesImpl;
-
- /**
- * 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.
- *
- */
- public class MMetricsStreamHandler implements ExecuteStreamHandler {
-
- /** CLASS construct, it should be named something like 'MyClass' */
- private static final String CLASS = "class";
-
- /** package construct, it should be look like 'com.mycompany.something' */
- private static final String PACKAGE = "package";
-
- /** FILE construct, it should look like something 'MyClass.java' or 'MyClass.class' */
- private static final String FILE = "file";
-
- /** METHOD construct, it should looke like something 'doSomething(...)' or 'doSomething()' */
- private static final String METHOD = "method";
-
- private static final String[] ATTRIBUTES = {
- "name", "vg", "loc", "dit", "noa", "nrm", "nlm", "wmc",
- "rfc", "dac", "fanout", "cbo", "lcom", "nocl"};
-
- /** reader for stdout */
- private 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
- */
- private OutputStream xmlOutputStream;
-
- /** metrics handler */
- private TransformerHandler metricsHandler;
-
- /** the task */
- private 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.
- */
- private 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
- final Date now = new Date();
- metricsHandler.startDocument();
- AttributesImpl attr = new AttributesImpl();
- attr.addAttribute("", "company", "company", "CDATA", "metamata");
- attr.addAttribute("", "snapshot_created", "snapshot_created", "CDATA",
- DateUtils.format(now, DateUtils.ISO8601_DATETIME_PATTERN));
- // attr.addAttribute("", "elapsed_time", "elapsed_time", "CDATA",
- // String.valueOf(now.getTime() - program_start.getTime()));
- attr.addAttribute("", "program_start", "program_start", "CDATA",
- DateUtils.format(new Date(), DateUtils.ISO8601_DATETIME_PATTERN));
- metricsHandler.startElement("", "metrics", "metrics", attr);
-
- // now parse the whole thing
- parseOutput();
-
- } catch (Exception e) {
- throw new BuildException(e);
- }
- }
-
- /**
- * 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
- */
- 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 static final 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 static final NumberFormat METAMATA_NF;
-
- private static final 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.elementAt(0);
- metrics.removeElementAt(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);
- }
- }
-