Browse Source

- Added rulespath to Audit to use customized rules.

- Fix Audit messages to remove fullpath in it. It
won't work on references though.
- Fix MAudit handler to print stderr as well so
that a missing agent or invalid options return
a message.
- Enhance options to reflect those available also
in Webgain QA 2.1.2+


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271226 13f79535-47bb-0310-9956-ffa450edef68
master
Stephane Bailliez 23 years ago
parent
commit
8af1905a98
5 changed files with 470 additions and 323 deletions
  1. +90
    -60
      src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java
  2. +108
    -56
      src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java
  3. +115
    -69
      src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java
  4. +62
    -56
      src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java
  5. +95
    -82
      src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java

+ 90
- 60
src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java View File

@@ -1,7 +1,7 @@
/* /*
* The Apache Software License, Version 1.1 * The Apache Software License, Version 1.1
* *
* Copyright (c) 2001 The Apache Software Foundation. All rights
* Copyright (c) 2001-2002 The Apache Software Foundation. All rights
* reserved. * reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@@ -53,25 +53,25 @@
*/ */
package org.apache.tools.ant.taskdefs.optional.metamata; package org.apache.tools.ant.taskdefs.optional.metamata;


import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.apache.tools.ant.BuildException; import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project; import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task; import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
import org.apache.tools.ant.taskdefs.Execute; import org.apache.tools.ant.taskdefs.Execute;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.CommandlineJava;
import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
import org.apache.tools.ant.types.Commandline; import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.types.CommandlineJava;
import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.DirectoryScanner;

import java.io.File;
import java.io.IOException;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Random;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.util.FileUtils;


/** /**
* Somewhat abstract framework to be used for other metama 2.0 tasks. * Somewhat abstract framework to be used for other metama 2.0 tasks.
@@ -80,11 +80,9 @@ import java.util.Random;
* For more information, visit the website at * For more information, visit the website at
* <a href="http://www.metamata.com">www.metamata.com</a> * <a href="http://www.metamata.com">www.metamata.com</a>
* *
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/ */
public abstract class AbstractMetamataTask extends Task{

//--------------------------- ATTRIBUTES -----------------------------------
public abstract class AbstractMetamataTask extends Task {


/** /**
* The user classpath to be provided. It matches the -classpath of the * The user classpath to be provided. It matches the -classpath of the
@@ -116,7 +114,7 @@ public abstract class AbstractMetamataTask extends Task{
// be set when calling scanFileSets(); // be set when calling scanFileSets();
protected Hashtable includedFiles = null; protected Hashtable includedFiles = null;


public AbstractMetamataTask(){
public AbstractMetamataTask() {
} }


/** initialize the task with the classname of the task to run */ /** initialize the task with the classname of the task to run */
@@ -126,8 +124,12 @@ public abstract class AbstractMetamataTask extends Task{
} }


/** the metamata.home property to run all tasks. */ /** the metamata.home property to run all tasks. */
public void setMetamatahome(final File metamataHome){
this.metamataHome = metamataHome;
public void setHome(final File value) {
this.metamataHome = value;
}

public void setMetamatahome(final File value) {
setHome(value);
} }


/** user classpath */ /** user classpath */
@@ -139,8 +141,8 @@ public abstract class AbstractMetamataTask extends Task{
} }


/** create the source path for this task */ /** create the source path for this task */
public Path createSourcepath(){
if (sourcePath == null){
public Path createSourcepath() {
if (sourcePath == null) {
sourcePath = new Path(project); sourcePath = new Path(project);
} }
return sourcePath; return sourcePath;
@@ -152,7 +154,7 @@ public abstract class AbstractMetamataTask extends Task{
} }


/** -mx or -Xmx depending on VM version */ /** -mx or -Xmx depending on VM version */
public void setMaxmemory(String max){
public void setMaxmemory(String max) {
if (Project.getJavaVersion().startsWith("1.1")) { if (Project.getJavaVersion().startsWith("1.1")) {
createJvmarg().setValue("-mx" + max); createJvmarg().setValue("-mx" + max);
} else { } else {
@@ -177,8 +179,6 @@ public abstract class AbstractMetamataTask extends Task{
} }
} }


//--------------------- PRIVATE/PROTECTED METHODS --------------------------

/** check the options and build the command line */ /** check the options and build the command line */
protected void setUp() throws BuildException { protected void setUp() throws BuildException {
checkOptions(); checkOptions();
@@ -190,10 +190,12 @@ public abstract class AbstractMetamataTask extends Task{


// set the metamata.home property // set the metamata.home property
final Commandline.Argument vmArgs = cmdl.createVmArgument(); final Commandline.Argument vmArgs = cmdl.createVmArgument();
vmArgs.setValue("-Dmetamata.home=" + metamataHome.getAbsolutePath() );
vmArgs.setValue("-Dmetamata.home=" + metamataHome.getAbsolutePath());


// retrieve all the files we want to scan // retrieve all the files we want to scan
includedFiles = scanFileSets();
includedFiles = scanSources(new Hashtable());
//String[] entries = sourcePath.list();
//includedFiles = scanSources(new Hashtable(), entries);
log(includedFiles.size() + " files added for audit", Project.MSG_VERBOSE); 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 // write all the options to a temp file and use it ro run the process
@@ -220,34 +222,33 @@ public abstract class AbstractMetamataTask extends Task{
if (process.execute() != 0) { if (process.execute() != 0) {
throw new BuildException("Metamata task failed."); throw new BuildException("Metamata task failed.");
} }
} catch (IOException e){
throw new BuildException("Failed to launch Metamata task: " + e);
} catch (IOException e) {
throw new BuildException("Failed to launch Metamata task", e);
} }
} }


/** clean up all the mess that we did with temporary objects */ /** clean up all the mess that we did with temporary objects */
protected void cleanUp(){
if (optionsFile != null){
protected void cleanUp() {
if (optionsFile != null) {
optionsFile.delete(); optionsFile.delete();
optionsFile = null; optionsFile = null;
} }
} }


/** return the location of the jar file used to run */ /** return the location of the jar file used to run */
protected final File getMetamataJar(File home){
return new File(new File(home.getAbsolutePath()), "lib/metamata.jar");
protected final File getMetamataJar(File home) {
return new File(home, "lib/metamata.jar");
} }


/** validate options set */ /** validate options set */
protected void checkOptions() throws BuildException { protected void checkOptions() throws BuildException {
// do some validation first // do some validation first
if (metamataHome == null || !metamataHome.exists()){
throw new BuildException("'metamatahome' must point to Metamata home directory.");
if (metamataHome == null || !metamataHome.exists()) {
throw new BuildException("'home' must point to Metamata home directory.");
} }
metamataHome = project.resolveFile(metamataHome.getPath());
File jar = getMetamataJar(metamataHome); File jar = getMetamataJar(metamataHome);
if (!jar.exists()){
throw new BuildException( jar + " does not exist. Check your metamata installation.");
if (!jar.exists()) {
throw new BuildException(jar + " does not exist. Check your metamata installation.");
} }
} }


@@ -261,65 +262,94 @@ public abstract class AbstractMetamataTask extends Task{
fw = new FileWriter(tofile); fw = new FileWriter(tofile);
PrintWriter pw = new PrintWriter(fw); PrintWriter pw = new PrintWriter(fw);
final int size = options.size(); final int size = options.size();
for (int i = 0; i < size; i++){
pw.println( options.elementAt(i) );
for (int i = 0; i < size; i++) {
pw.println(options.elementAt(i));
} }
pw.flush(); pw.flush();
} catch (IOException e){
} catch (IOException e) {
throw new BuildException("Error while writing options file " + tofile, e); throw new BuildException("Error while writing options file " + tofile, e);
} finally { } finally {
if (fw != null){
if (fw != null) {
try { try {
fw.close(); fw.close();
} catch (IOException ignored){}
} catch (IOException ignored) {
}
} }
} }
} }




protected Hashtable getFileMapping(){
protected Hashtable getFileMapping() {
return includedFiles; return includedFiles;
} }

/** /**
* convenient method for JDK 1.1. Will copy all elements from src to dest * convenient method for JDK 1.1. Will copy all elements from src to dest
*/ */
protected static final void addAllVector(Vector dest, Enumeration files){
protected static final void addAllVector(Vector dest, Enumeration files) {
while (files.hasMoreElements()) { while (files.hasMoreElements()) {
dest.addElement( files.nextElement() );
dest.addElement(files.nextElement());
} }
} }
protected static final 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;

protected final File createTmpFile() {
return FileUtils.newFileUtils().createTempFile("metamata", ".tmp", getProject().getBaseDir());
} }


/** /**
* @return the list of .java files (as their absolute path) that should * @return the list of .java files (as their absolute path) that should
* be audited. * be audited.
*/ */
protected Hashtable scanFileSets(){

protected Hashtable scanSources(Hashtable map) {
Hashtable files = new Hashtable(); Hashtable files = new Hashtable();
for (int i = 0; i < fileSets.size(); i++){
for (int i = 0; i < fileSets.size(); i++) {
FileSet fs = (FileSet) fileSets.elementAt(i); FileSet fs = (FileSet) fileSets.elementAt(i);
DirectoryScanner ds = fs.getDirectoryScanner(project); DirectoryScanner ds = fs.getDirectoryScanner(project);
ds.scan(); ds.scan();
String[] f = ds.getIncludedFiles(); String[] f = ds.getIncludedFiles();
log(i + ") Adding " + f.length + " files from directory " + ds.getBasedir(), Project.MSG_VERBOSE); log(i + ") Adding " + f.length + " files from directory " + ds.getBasedir(), Project.MSG_VERBOSE);
for (int j = 0; j < f.length; j++){
for (int j = 0; j < f.length; j++) {
String pathname = f[j]; String pathname = f[j];
if ( pathname.endsWith(".java") ){
File file = new File( ds.getBasedir(), pathname);
if (pathname.endsWith(".java")) {
File file = new File(ds.getBasedir(), pathname);
// file = project.resolveFile(file.getAbsolutePath()); // file = project.resolveFile(file.getAbsolutePath());
String classname = pathname.substring(0, pathname.length()-".java".length());
String classname = pathname.substring(0, pathname.length() - ".java".length());
classname = classname.replace(File.separatorChar, '.'); classname = classname.replace(File.separatorChar, '.');
files.put( file.getAbsolutePath(), classname ); // it's a java file, add it.
files.put(file.getAbsolutePath(), classname); // it's a java file, add it.
} }
} }
} }
return files; return files;
} }


protected Hashtable scanSources(final Hashtable mapping, final String[] entries) {
final Vector javaFiles = new Vector(512);
for (int i = 0; i < entries.length; i++) {
final File f = new File(entries[i]);
if (f.isDirectory()) {
DirectoryScanner ds = new DirectoryScanner();
ds.setBasedir(f);
ds.setIncludes(new String[]{"**/*.java"});
ds.scan();
String[] included = ds.getIncludedFiles();
for (int j = 0; j < included.length; j++) {
javaFiles.addElement(new File(f, included[j]));
}
} else if (entries[i].endsWith(".java")) {
javaFiles.addElement(f);
}
}
// do the mapping paths/classname
final int count = javaFiles.size();
for (int i = 0; i < count; i++) {
File file = (File) javaFiles.elementAt(i);
String pathname = Path.translateFile(file.getAbsolutePath());
String classname = pathname.substring(0, pathname.length() - ".java".length());
classname = classname.replace(File.separatorChar, '.');
mapping.put(pathname, classname);
}
return mapping;
}

} }

+ 108
- 56
src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java View File

@@ -1,7 +1,7 @@
/* /*
* The Apache Software License, Version 1.1 * The Apache Software License, Version 1.1
* *
* Copyright (c) 2001 The Apache Software Foundation. All rights
* Copyright (c) 2001-2002 The Apache Software Foundation. All rights
* reserved. * reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@@ -53,21 +53,19 @@
*/ */
package org.apache.tools.ant.taskdefs.optional.metamata; package org.apache.tools.ant.taskdefs.optional.metamata;


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Vector;

import org.apache.tools.ant.BuildException; import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project; import org.apache.tools.ant.Project;

import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
import org.apache.tools.ant.taskdefs.LogStreamHandler; import org.apache.tools.ant.taskdefs.LogStreamHandler;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Path;




import java.io.File;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Vector;

/** /**
* Metamata Audit evaluates Java code for programming errors, weaknesses, and * Metamata Audit evaluates Java code for programming errors, weaknesses, and
* style violation. * style violation.
@@ -81,7 +79,7 @@ import java.util.Vector;
* For more information, visit the website at * For more information, visit the website at
* <a href="http://www.metamata.com">www.metamata.com</a> * <a href="http://www.metamata.com">www.metamata.com</a>
* *
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/ */
public class MAudit extends AbstractMetamataTask { public class MAudit extends AbstractMetamataTask {


@@ -119,15 +117,24 @@ public class MAudit extends AbstractMetamataTask {
// (?:file:)?((?#filepath).+):((?#line)\\d+)\\s*:\\s+((?#message).*) // (?:file:)?((?#filepath).+):((?#line)\\d+)\\s*:\\s+((?#message).*)
final static String AUDIT_PATTERN = "(?:file:)?(.+):(\\d+)\\s*:\\s+(.*)"; final static 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;
private File outFile = null;

private Path searchPath = null;

private Path rulesPath = null;

private boolean fix = false;

private boolean list = false;

private boolean unused = false;

// add a bunch of undocumented options for the task
private boolean quiet = false;
private boolean exit = false;
private boolean offsets = false;
private boolean verbose = false;
private boolean fullsemanticize = false;


/** default constructor */ /** default constructor */
public MAudit() { public MAudit() {
@@ -135,49 +142,101 @@ public class MAudit extends AbstractMetamataTask {
} }


/** set the destination file which should be an xml file */ /** set the destination file which should be an xml file */
public void setTofile(File outFile){
public void setTofile(File outFile) {
this.outFile = outFile; this.outFile = outFile;
} }


public void setFix(boolean flag){
public void setFix(boolean flag) {
this.fix = flag; this.fix = flag;
} }


public void setList(boolean flag){
public void setList(boolean flag) {
this.list = flag; this.list = flag;
} }


public void setUnused(boolean flag){
public void setUnused(boolean flag) {
this.unused = flag; this.unused = flag;
} }


public Path createSearchpath(){
if (searchPath == null){
searchPath = new Path(project);
public void setQuiet(boolean flag) {
this.quiet = flag;
}

public void setExit(boolean flag) {
this.exit = flag;
}

public void setOffsets(boolean flag) {
this.offsets = flag;
}

public void setVerbose(boolean flag) {
this.verbose = flag;
}

public void setFullsemanticize(boolean flag) {
this.fullsemanticize = flag;
}

/** one or more path for rules that must be placed before metamata.jar !! */
public Path createRulespath() {
if (rulesPath == null) {
rulesPath = new Path(getProject());
}
return rulesPath;
}

/** search path to use for unused global declarations */
public Path createSearchpath() {
if (searchPath == null) {
searchPath = new Path(getProject());
} }
return searchPath; return searchPath;
} }


protected Vector getOptions(){
protected Vector getOptions() {
Vector options = new Vector(512); Vector options = new Vector(512);
// add the source path automatically from the fileset.
// to avoid redundancy...
for (int i = 0; i < fileSets.size(); i++) {
FileSet fs = (FileSet) fileSets.elementAt(i);
Path path = createSourcepath();
File dir = fs.getDir(getProject());
path.setLocation(dir);
}

// there is a bug in Metamata 2.0 build 37. The sourcepath argument does // 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 // not work. So we will use the sourcepath prepended to classpath. (order
// is important since Metamata looks at .class and .java) // is important since Metamata looks at .class and .java)
if (sourcePath != null){
if (sourcePath != null) {
sourcePath.append(classPath); // srcpath is prepended sourcePath.append(classPath); // srcpath is prepended
classPath = sourcePath; classPath = sourcePath;
sourcePath = null; // prevent from using -sourcepath sourcePath = null; // prevent from using -sourcepath
}
}
// don't forget to modify the pattern if you change the options reporting // don't forget to modify the pattern if you change the options reporting
if (classPath != null){
if (classPath != null) {
options.addElement("-classpath"); options.addElement("-classpath");
options.addElement(classPath.toString()); options.addElement(classPath.toString());
} }
// suppress copyright msg when running, we will let it so that this // suppress copyright msg when running, we will let it so that this
// will be the only output to the console if in xml mode // will be the only output to the console if in xml mode
// options.addElement("-quiet");
if (fix){
if (quiet) {
options.addElement("-quiet");
}
if (fullsemanticize) {
options.addElement("-full-semanticize");
}
if (verbose) {
options.addElement("-verbose");
}
if (offsets) {
options.addElement("-offsets");
}
if (exit) {
options.addElement("-exit");
}
if (fix) {
options.addElement("-fix"); options.addElement("-fix");
} }
options.addElement("-fullpath"); options.addElement("-fullpath");
@@ -185,43 +244,45 @@ public class MAudit extends AbstractMetamataTask {
// generate .maudit files much more detailed than the report // generate .maudit files much more detailed than the report
// I don't like it very much, I think it could be interesting // I don't like it very much, I think it could be interesting
// to get all .maudit files and include them in the XML. // to get all .maudit files and include them in the XML.
if (list){
if (list) {
options.addElement("-list"); options.addElement("-list");
} }
if (sourcePath != null){
if (sourcePath != null) {
options.addElement("-sourcepath"); options.addElement("-sourcepath");
options.addElement(sourcePath.toString()); options.addElement(sourcePath.toString());
} }
if (unused){
addAllVector(options, includedFiles.keys());
if (unused) {
options.addElement("-unused"); options.addElement("-unused");
options.addElement(searchPath.toString()); options.addElement(searchPath.toString());
} }
addAllVector(options, includedFiles.keys());
return options; return options;
} }


protected void checkOptions() throws BuildException { protected void checkOptions() throws BuildException {
super.checkOptions(); super.checkOptions();
if (unused && searchPath == null){
if (unused && searchPath == null) {
throw new BuildException("'searchpath' element must be set when looking for 'unused' declarations."); throw new BuildException("'searchpath' element must be set when looking for 'unused' declarations.");
} }
if (!unused && searchPath != null){
if (!unused && searchPath != null) {
log("'searchpath' element ignored. 'unused' attribute is disabled.", Project.MSG_WARN); log("'searchpath' element ignored. 'unused' attribute is disabled.", Project.MSG_WARN);
} }
if (rulesPath != null) {
cmdl.createClasspath(getProject()).addExisting(rulesPath);
}
} }
protected ExecuteStreamHandler createStreamHandler() throws BuildException { protected ExecuteStreamHandler createStreamHandler() throws BuildException {
ExecuteStreamHandler handler = null; ExecuteStreamHandler handler = null;
// if we didn't specify a file, then use a screen report // 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);
if (outFile == null) {
handler = new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_INFO);
} else { } else {
try { try {
//XXX //XXX
OutputStream out = new FileOutputStream( outFile );
OutputStream out = new FileOutputStream(outFile);
handler = new MAuditStreamHandler(this, out); handler = new MAuditStreamHandler(this, out);
} catch (IOException e){
} catch (IOException e) {
throw new BuildException(e); throw new BuildException(e);
} }
} }
@@ -242,18 +303,9 @@ public class MAudit extends AbstractMetamataTask {


/** the inner class used to report violation information */ /** the inner class used to report violation information */
final static class Violation { final static class Violation {
int line;
String line;
String error; 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;
}

} }




+ 115
- 69
src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java View File

@@ -1,7 +1,7 @@
/* /*
* The Apache Software License, Version 1.1 * The Apache Software License, Version 1.1
* *
* Copyright (c) 2001 The Apache Software Foundation. All rights
* Copyright (c) 2001-2002 The Apache Software Foundation. All rights
* reserved. * reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@@ -53,31 +53,34 @@
*/ */
package org.apache.tools.ant.taskdefs.optional.metamata; package org.apache.tools.ant.taskdefs.optional.metamata;


import org.apache.tools.ant.Project;

import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;

import org.apache.tools.ant.util.regexp.RegexpMatcher;
import org.apache.tools.ant.util.regexp.RegexpMatcherFactory;

import org.apache.tools.ant.util.DOMElementWriter;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Writer;
import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.util.Hashtable;
import java.io.Writer;
import java.util.Date;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector; import java.util.Vector;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;


import org.w3c.dom.Document;
import org.w3c.dom.Element;

import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
import org.apache.tools.ant.taskdefs.LogOutputStream;
import org.apache.tools.ant.taskdefs.StreamPumper;
import org.apache.tools.ant.util.DOMElementWriter;
import org.apache.tools.ant.util.DateUtils;
import org.apache.tools.ant.util.StringUtils;
import org.apache.tools.ant.util.regexp.RegexpMatcher;
import org.apache.tools.ant.util.regexp.RegexpMatcherFactory;



/** /**
* This is a very bad stream handler for the MAudit task. * This is a very bad stream handler for the MAudit task.
@@ -93,31 +96,41 @@ import javax.xml.parsers.DocumentBuilderFactory;
* <li>it does not report error that goes to stderr. * <li>it does not report error that goes to stderr.
* </ul> * </ul>
* *
* @author <a href="sbailliez@imediation.com">Stephane Bailliez</a>
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
*/ */
class MAuditStreamHandler implements ExecuteStreamHandler { class MAuditStreamHandler implements ExecuteStreamHandler {


protected MAudit task;
/** parent task */
private MAudit task;


/** reader for stdout */ /** reader for stdout */
protected BufferedReader br;
private BufferedReader br;


/** matcher that will be used to extract the info from the line */ /** matcher that will be used to extract the info from the line */
protected RegexpMatcher matcher;
private RegexpMatcher matcher;


/** /**
* this is where the XML output will go, should mostly be a file * this is where the XML output will go, should mostly be a file
* the caller is responsible for flushing and closing this stream * the caller is responsible for flushing and closing this stream
*/ */
protected OutputStream xmlOut = null;
private OutputStream xmlOut = null;

/** error stream, might be useful to spit out error messages */
private OutputStream errStream;

/** thread pumping out error stream */
private Thread errThread;


/** /**
* the multimap. The key in the map is the filepath that caused the audit * 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. * error and the value is a vector of MAudit.Violation entries.
*/ */
protected Hashtable auditedFiles = new Hashtable();
private Hashtable auditedFiles = new Hashtable();


MAuditStreamHandler(MAudit task, OutputStream xmlOut){
/** program start timestamp for reporting purpose */
private Date program_start;

MAuditStreamHandler(MAudit task, OutputStream xmlOut) {
this.task = task; this.task = task;
this.xmlOut = xmlOut; this.xmlOut = xmlOut;
/** the matcher should be the Oro one. I don't know about the other one */ /** the matcher should be the Oro one. I don't know about the other one */
@@ -126,10 +139,14 @@ class MAuditStreamHandler implements ExecuteStreamHandler {
} }


/** Ignore. */ /** Ignore. */
public void setProcessInputStream(OutputStream os) {}
public void setProcessInputStream(OutputStream os) {
}


/** Ignore. */ /** Ignore. */
public void setProcessErrorStream(InputStream is) {}
public void setProcessErrorStream(InputStream is) {
errStream = new LogOutputStream(task, Project.MSG_ERR);
errThread = createPump(is, errStream);
}


/** Set the inputstream */ /** Set the inputstream */
public void setProcessOutputStream(InputStream is) throws IOException { public void setProcessOutputStream(InputStream is) throws IOException {
@@ -138,6 +155,8 @@ class MAuditStreamHandler implements ExecuteStreamHandler {


/** Invokes parseOutput. This will block until the end :-(*/ /** Invokes parseOutput. This will block until the end :-(*/
public void start() throws IOException { public void start() throws IOException {
program_start = new Date();
errThread.start();
parseOutput(br); parseOutput(br);
} }


@@ -146,19 +165,32 @@ class MAuditStreamHandler implements ExecuteStreamHandler {
* the MAudit output and write it to the output. * the MAudit output and write it to the output.
*/ */
public void stop() { public void stop() {
// make sure to flush err stream
try {
errThread.join();
} catch (InterruptedException e) {
}
try {
errStream.flush();
} catch (IOException e) {
}
// serialize the content as XML, move this to another method // serialize the content as XML, move this to another method
// this is the only code that could be needed to be overrided
// this is the only code that could be needed to be overriden
Document doc = getDocumentBuilder().newDocument(); Document doc = getDocumentBuilder().newDocument();
Element rootElement = doc.createElement("classes"); Element rootElement = doc.createElement("classes");
Enumeration keys = auditedFiles.keys(); Enumeration keys = auditedFiles.keys();
Hashtable filemapping = task.getFileMapping(); Hashtable filemapping = task.getFileMapping();
final Date now = new Date();
rootElement.setAttribute("snapshot_created", DateUtils.format(now, DateUtils.ISO8601_DATETIME_PATTERN));
rootElement.setAttribute("elapsed_time", String.valueOf(now.getTime() - program_start.getTime()));
rootElement.setAttribute("program_start", DateUtils.format(now, DateUtils.ISO8601_DATETIME_PATTERN));
rootElement.setAttribute("audited", String.valueOf(filemapping.size())); rootElement.setAttribute("audited", String.valueOf(filemapping.size()));
rootElement.setAttribute("reported", String.valueOf(auditedFiles.size())); rootElement.setAttribute("reported", String.valueOf(auditedFiles.size()));
int errors = 0; int errors = 0;
while (keys.hasMoreElements()){
String filepath = (String)keys.nextElement();
Vector v = (Vector)auditedFiles.get(filepath);
String fullclassname = (String)filemapping.get(filepath);
while (keys.hasMoreElements()) {
String filepath = (String) keys.nextElement();
Vector v = (Vector) auditedFiles.get(filepath);
String fullclassname = (String) filemapping.get(filepath);
if (fullclassname == null) { if (fullclassname == null) {
task.getProject().log("Could not find class mapping for " + filepath, Project.MSG_WARN); task.getProject().log("Could not find class mapping for " + filepath, Project.MSG_WARN);
continue; continue;
@@ -169,12 +201,13 @@ class MAuditStreamHandler implements ExecuteStreamHandler {
Element clazz = doc.createElement("class"); Element clazz = doc.createElement("class");
clazz.setAttribute("package", pkg); clazz.setAttribute("package", pkg);
clazz.setAttribute("name", clazzname); 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);
final int violationCount = v.size();
clazz.setAttribute("violations", String.valueOf(violationCount));
errors += violationCount;
for (int i = 0; i < violationCount; i++) {
MAudit.Violation violation = (MAudit.Violation) v.elementAt(i);
Element error = doc.createElement("violation"); Element error = doc.createElement("violation");
error.setAttribute("line", String.valueOf(violation.line));
error.setAttribute("line", violation.line);
error.setAttribute("message", violation.error); error.setAttribute("message", violation.error);
clazz.appendChild(error); clazz.appendChild(error);
} }
@@ -183,54 +216,67 @@ class MAuditStreamHandler implements ExecuteStreamHandler {
rootElement.setAttribute("violations", String.valueOf(errors)); rootElement.setAttribute("violations", String.valueOf(errors));


// now write it to the outputstream, not very nice code // 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) {}
}
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 (wri != null) {
try {
wri.close();
} catch (IOException e) {
} }
} }
} }

} }


protected static DocumentBuilder getDocumentBuilder() { protected static DocumentBuilder getDocumentBuilder() {
try { try {
return DocumentBuilderFactory.newInstance().newDocumentBuilder(); return DocumentBuilderFactory.newInstance().newDocumentBuilder();
}
catch(Exception exc) {
} catch (Exception exc) {
throw new ExceptionInInitializerError(exc); throw new ExceptionInInitializerError(exc);
} }
} }


/**
* Creates a stream pumper to copy the given input stream to the given output stream.
*/
protected Thread createPump(InputStream is, OutputStream os) {
final Thread result = new Thread(new StreamPumper(is, os));
result.setDaemon(true);
return result;
}


/** read each line and process it */ /** read each line and process it */
protected void parseOutput(BufferedReader br) throws IOException { protected void parseOutput(BufferedReader br) throws IOException {
String line = null; String line = null;
while ( (line = br.readLine()) != null ){
while ((line = br.readLine()) != null) {
processLine(line); processLine(line);
} }
} }


// we suppose here that there is only one report / line. // we suppose here that there is only one report / line.
// There will obviouslly be a problem if the message is on several lines... // There will obviouslly be a problem if the message is on several lines...
protected void processLine(String line){
protected void processLine(String line) {
Vector matches = matcher.getGroups(line); Vector matches = matcher.getGroups(line);
if (matches != null) { 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) );
String file = (String) matches.elementAt(1);
MAudit.Violation violation = new MAudit.Violation();
violation.line = (String) matches.elementAt(2);
violation.error = (String) matches.elementAt(3);
// remove the pathname from any messages and let the classname only.
final int pos = file.lastIndexOf(File.separatorChar);
if ((pos != -1) && (pos != file.length() - 1)) {
String filename = file.substring(pos + 1);
violation.error = StringUtils.replace(violation.error,
"file:" + file, filename);
}
addViolationEntry(file, violation);
} else { } else {
// this doesn't match..report it as info, it could be // this doesn't match..report it as info, it could be
// either the copyright, summary or a multiline message (damn !) // either the copyright, summary or a multiline message (damn !)
@@ -239,14 +285,14 @@ class MAuditStreamHandler implements ExecuteStreamHandler {
} }


/** add a violation entry for the file */ /** 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 );
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.addElement(entry);
} }


} }

+ 62
- 56
src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java View File

@@ -53,21 +53,19 @@
*/ */
package org.apache.tools.ant.taskdefs.optional.metamata; package org.apache.tools.ant.taskdefs.optional.metamata;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Vector;

import org.apache.tools.ant.BuildException; import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project; import org.apache.tools.ant.Project;

import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
import org.apache.tools.ant.taskdefs.LogStreamHandler; import org.apache.tools.ant.taskdefs.LogStreamHandler;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Path;




import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.FileOutputStream;
import java.util.Vector;

/** /**
* Calculates global complexity and quality metrics on Java source code. * Calculates global complexity and quality metrics on Java source code.
* *
@@ -114,15 +112,15 @@ Format Options
*/ */


/** the granularity mode. Should be one of 'files', 'methods' and 'types'. */ /** the granularity mode. Should be one of 'files', 'methods' and 'types'. */
protected String granularity = null;
private String granularity = null;


/** the XML output file */ /** the XML output file */
protected File outFile = null;
private File outFile = null;
/** the location of the temporary txt report */ /** the location of the temporary txt report */
protected File tmpFile = createTmpFile();
private File tmpFile;


protected Path path = null;
private Path path = null;


//--------------------------- PUBLIC METHODS ------------------------------- //--------------------------- PUBLIC METHODS -------------------------------


@@ -131,20 +129,29 @@ Format Options
super("com.metamata.sc.MMetrics"); super("com.metamata.sc.MMetrics");
} }


/**
* Attributes for granularity.
*/
public static class GranularityAttribute extends EnumeratedAttribute {
public String[] getValues() {
return new String[]{"compilation-units", "files", "methods", "types", "packages"};
}
}

/** /**
* set the granularity of the audit. Should be one of 'files', 'methods' * set the granularity of the audit. Should be one of 'files', 'methods'
* or 'types'. * or 'types'.
* @param granularity the audit reporting mode. * @param granularity the audit reporting mode.
*/ */
public void setGranularity(String granularity){
this.granularity = granularity;
public void setGranularity(GranularityAttribute granularity) {
this.granularity = granularity.getValue();
} }


/** /**
* Set the output XML file * Set the output XML file
* @param file the xml file to write the XML report to. * @param file the xml file to write the XML report to.
*/ */
public void setTofile(File file){
public void setTofile(File file) {
this.outFile = file; this.outFile = file;
} }


@@ -152,7 +159,7 @@ Format Options
* Set a new path (directory) to measure metrics from. * Set a new path (directory) to measure metrics from.
* @return the path instance to use. * @return the path instance to use.
*/ */
public Path createPath(){
public Path createPath() {
if (path == null) { if (path == null) {
path = new Path(project); path = new Path(project);
} }
@@ -167,27 +174,24 @@ Format Options
protected void checkOptions() throws BuildException { protected void checkOptions() throws BuildException {
super.checkOptions(); 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){
if (outFile == null) {
throw new BuildException("Output XML file must be set via 'tofile' attribute."); throw new BuildException("Output XML file must be set via 'tofile' attribute.");
} }
if (path == null && fileSets.size() == 0){
if (path == null && fileSets.size() == 0) {
throw new BuildException("Must set either paths (path element) or files (fileset element)"); 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 // 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){
if (path != null && fileSets.size() > 0) {
throw new BuildException("Cannot set paths (path element) and files (fileset element) at the same time"); throw new BuildException("Cannot set paths (path element) and files (fileset element) at the same time");
} }
tmpFile = createTmpFile();
} }


protected void execute0(ExecuteStreamHandler handler) throws BuildException { protected void execute0(ExecuteStreamHandler handler) throws BuildException {
super.execute0(handler); super.execute0(handler);
transformFile(); transformFile();
} }
/** /**
* transform the generated file via the handler * transform the generated file via the handler
* This function can either be called if the result is written to the output * This function can either be called if the result is written to the output
@@ -197,8 +201,8 @@ Format Options
protected void transformFile() throws BuildException { protected void transformFile() throws BuildException {
FileInputStream tmpStream = null; FileInputStream tmpStream = null;
try { try {
tmpStream = new FileInputStream( tmpFile );
} catch (IOException e){
tmpStream = new FileInputStream(tmpFile);
} catch (IOException e) {
throw new BuildException("Error reading temporary file: " + tmpFile, e); throw new BuildException("Error reading temporary file: " + tmpFile, e);
} }
FileOutputStream xmlStream = null; FileOutputStream xmlStream = null;
@@ -208,29 +212,31 @@ Format Options
xmlHandler.setProcessOutputStream(tmpStream); xmlHandler.setProcessOutputStream(tmpStream);
xmlHandler.start(); xmlHandler.start();
xmlHandler.stop(); xmlHandler.stop();
} catch (IOException e){
} catch (IOException e) {
throw new BuildException("Error creating output file: " + outFile, e); throw new BuildException("Error creating output file: " + outFile, e);
} finally { } finally {
if (xmlStream != null){
if (xmlStream != null) {
try { try {
xmlStream.close(); xmlStream.close();
} catch (IOException ignored){}
} catch (IOException ignored) {
}
} }
if (tmpStream != null){
if (tmpStream != null) {
try { try {
tmpStream.close(); tmpStream.close();
} catch (IOException ignored){}
} catch (IOException ignored) {
}
} }
} }
} }


/** cleanup the temporary txt report */ /** cleanup the temporary txt report */
protected void cleanUp() throws BuildException { protected void cleanUp() throws BuildException {
try { try {
super.cleanUp(); super.cleanUp();
} finally { } finally {
if (tmpFile != null){
if (tmpFile != null) {
tmpFile.delete(); tmpFile.delete();
tmpFile = null; tmpFile = null;
} }
@@ -242,52 +248,52 @@ Format Options
* a normal logger here, otherwise we could use the metrics handler * a normal logger here, otherwise we could use the metrics handler
* directly to capture and transform the output on stdout to XML. * directly to capture and transform the output on stdout to XML.
*/ */
protected ExecuteStreamHandler createStreamHandler(){
protected ExecuteStreamHandler createStreamHandler() {
// write the report directtly to an XML stream // write the report directtly to an XML stream
// return new MMetricsStreamHandler(this, xmlStream); // return new MMetricsStreamHandler(this, xmlStream);
return new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_INFO); return new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_INFO);
} }




protected Vector getOptions(){
protected Vector getOptions() {
Vector options = new Vector(512); Vector options = new Vector(512);
// there is a bug in Metamata 2.0 build 37. The sourcepath argument does // 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 // not work. So we will use the sourcepath prepended to classpath. (order
// is important since Metamata looks at .class and .java) // is important since Metamata looks at .class and .java)
if (sourcePath != null){
if (sourcePath != null) {
sourcePath.append(classPath); // srcpath is prepended sourcePath.append(classPath); // srcpath is prepended
classPath = sourcePath; classPath = sourcePath;
sourcePath = null; // prevent from using -sourcepath sourcePath = null; // prevent from using -sourcepath
} }
// don't forget to modify the pattern if you change the options reporting // don't forget to modify the pattern if you change the options reporting
if (classPath != null){
if (classPath != null) {
options.addElement("-classpath"); options.addElement("-classpath");
options.addElement(classPath);
options.addElement(classPath.toString());
} }
options.addElement( "-output" );
options.addElement( tmpFile.toString() );
options.addElement( "-" + granularity);
options.addElement("-output");
options.addElement(tmpFile.toString());
options.addElement("-" + granularity);
// display the metamata copyright // display the metamata copyright
// options.addElement( "-quiet"); // options.addElement( "-quiet");
options.addElement( "-format");
options.addElement("-format");
// need this because that's what the handler is using, it's // need this because that's what the handler is using, it's
// way easier to process than any other separator // way easier to process than any other separator
options.addElement( "tab");
options.addElement("tab");
// specify a / as the indent character, used by the handler. // specify a / as the indent character, used by the handler.
options.addElement( "-i");
options.addElement( "/");
options.addElement("-i");
options.addElement("/");
// directories // directories
String[] dirs = path.list(); String[] dirs = path.list();
for (int i = 0; i < dirs.length; i++){
options.addElement( dirs[i] );
for (int i = 0; i < dirs.length; i++) {
options.addElement(dirs[i]);
} }
// files next.
// files next.
addAllVector(options, includedFiles.keys()); addAllVector(options, includedFiles.keys());
return options; return options;
} }


+ 95
- 82
src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java View File

@@ -54,32 +54,36 @@
package org.apache.tools.ant.taskdefs.optional.metamata; package org.apache.tools.ant.taskdefs.optional.metamata;




import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.sax.SAXTransformerFactory;
import java.util.Stack;
import java.util.EmptyStackException;
import java.util.Enumeration;
import java.util.Vector;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.text.NumberFormat;
import java.text.DecimalFormat; 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.taskdefs.ExecuteStreamHandler;
import org.apache.tools.ant.Task;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project; 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;


/** /**
* A handy metrics handler. Most of this code was done only with the * A handy metrics handler. Most of this code was done only with the
@@ -95,44 +99,44 @@ import org.apache.tools.ant.Project;
public class MMetricsStreamHandler implements ExecuteStreamHandler { public class MMetricsStreamHandler implements ExecuteStreamHandler {


/** CLASS construct, it should be named something like 'MyClass' */ /** CLASS construct, it should be named something like 'MyClass' */
protected final static String CLASS = "class";
private final static String CLASS = "class";


/** package construct, it should be look like 'com.mycompany.something' */ /** package construct, it should be look like 'com.mycompany.something' */
protected final static String PACKAGE = "package";
private final static String PACKAGE = "package";


/** FILE construct, it should look like something 'MyClass.java' or 'MyClass.class' */ /** FILE construct, it should look like something 'MyClass.java' or 'MyClass.class' */
protected final static String FILE = "file";
private final static String FILE = "file";


/** METHOD construct, it should looke like something 'doSomething(...)' or 'doSomething()' */ /** METHOD construct, it should looke like something 'doSomething(...)' or 'doSomething()' */
protected final static String METHOD = "method";
private final static String METHOD = "method";


protected final static String[] ATTRIBUTES = { "name", "vg", "loc",
"dit", "noa", "nrm", "nlm", "wmc", "rfc", "dac", "fanout", "cbo", "lcom", "nocl"
};
private final static String[] ATTRIBUTES = {
"name", "vg", "loc", "dit", "noa", "nrm", "nlm", "wmc",
"rfc", "dac", "fanout", "cbo", "lcom", "nocl"};


/** reader for stdout */ /** reader for stdout */
protected InputStream metricsOutput;
private InputStream metricsOutput;


/** /**
* this is where the XML output will go, should mostly be a file * this is where the XML output will go, should mostly be a file
* the caller is responsible for flushing and closing this stream * the caller is responsible for flushing and closing this stream
*/ */
protected OutputStream xmlOutputStream;
private OutputStream xmlOutputStream;


/** metrics handler */ /** metrics handler */
protected TransformerHandler metricsHandler;
private TransformerHandler metricsHandler;


/** the task */ /** the task */
protected Task task;
private Task task;


/** /**
* the stack where are stored the metrics element so that they we can * the stack where are stored the metrics element so that they we can
* know if we have to close an element or not. * know if we have to close an element or not.
*/ */
protected Stack stack = new Stack();
private Stack stack = new Stack();


/** initialize this handler */ /** initialize this handler */
MMetricsStreamHandler(Task task, OutputStream xmlOut){
MMetricsStreamHandler(Task task, OutputStream xmlOut) {
this.task = task; this.task = task;
this.xmlOutputStream = xmlOut; this.xmlOutputStream = xmlOut;
} }
@@ -147,34 +151,39 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler {


/** Set the inputstream */ /** Set the inputstream */
public void setProcessOutputStream(InputStream is) throws IOException { public void setProcessOutputStream(InputStream is) throws IOException {
metricsOutput = is;
metricsOutput = is;
} }


public void start() throws IOException { public void start() throws IOException {
// create the transformer handler that will be used to serialize // create the transformer handler that will be used to serialize
// the output. // the output.
TransformerFactory factory = TransformerFactory.newInstance(); TransformerFactory factory = TransformerFactory.newInstance();
if ( !factory.getFeature(SAXTransformerFactory.FEATURE) ){
if (!factory.getFeature(SAXTransformerFactory.FEATURE)) {
throw new IllegalStateException("Invalid Transformer factory feature"); throw new IllegalStateException("Invalid Transformer factory feature");
} }
try { try {
metricsHandler = ((SAXTransformerFactory)factory).newTransformerHandler();
metricsHandler.setResult( new StreamResult( new OutputStreamWriter(xmlOutputStream, "UTF-8")) );
metricsHandler = ((SAXTransformerFactory) factory).newTransformerHandler();
metricsHandler.setResult(new StreamResult(new OutputStreamWriter(xmlOutputStream, "UTF-8")));
Transformer transformer = metricsHandler.getTransformer(); Transformer transformer = metricsHandler.getTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.INDENT, "yes");


// start the document with a 'metrics' root // start the document with a 'metrics' root
final Date now = new Date();
metricsHandler.startDocument(); metricsHandler.startDocument();
AttributesImpl attr = new AttributesImpl(); AttributesImpl attr = new AttributesImpl();
attr.addAttribute("", "company", "company", "CDATA", "metamata"); 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); metricsHandler.startElement("", "metrics", "metrics", attr);


// now parse the whole thing // now parse the whole thing
parseOutput(); parseOutput();


} catch (Exception e){
e.printStackTrace();
throw new IOException(e.getMessage());
} catch (Exception e) {
throw new BuildException(e);
} }
} }


@@ -185,15 +194,15 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler {
try { try {
// we need to pop everything and close elements that have not been // we need to pop everything and close elements that have not been
// closed yet. // closed yet.
while ( stack.size() > 0){
ElementEntry elem = (ElementEntry)stack.pop();
while (stack.size() > 0) {
ElementEntry elem = (ElementEntry) stack.pop();
metricsHandler.endElement("", elem.getType(), elem.getType()); metricsHandler.endElement("", elem.getType(), elem.getType());
} }
// close the root // close the root
metricsHandler.endElement("", "metrics", "metrics"); metricsHandler.endElement("", "metrics", "metrics");
// document is finished for good // document is finished for good
metricsHandler.endDocument(); metricsHandler.endDocument();
} catch (SAXException e){
} catch (SAXException e) {
e.printStackTrace(); e.printStackTrace();
throw new IllegalStateException(e.getMessage()); throw new IllegalStateException(e.getMessage());
} }
@@ -203,7 +212,7 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler {
protected void parseOutput() throws IOException, SAXException { protected void parseOutput() throws IOException, SAXException {
BufferedReader br = new BufferedReader(new InputStreamReader(metricsOutput)); BufferedReader br = new BufferedReader(new InputStreamReader(metricsOutput));
String line = null; String line = null;
while ( (line = br.readLine()) != null ){
while ((line = br.readLine()) != null) {
processLine(line); processLine(line);
} }
} }
@@ -214,16 +223,16 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler {
* @param line the line to process, it is normally a line full of metrics. * @param line the line to process, it is normally a line full of metrics.
*/ */
protected void processLine(String line) throws SAXException { 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") ){
if (line.startsWith("Construct\tV(G)\tLOC\tDIT\tNOA\tNRM\tNLM\tWMC\tRFC\tDAC\tFANOUT\tCBO\tLCOM\tNOCL")) {
return; return;
} }
try { try {
MetricsElement elem = MetricsElement.parse(line); MetricsElement elem = MetricsElement.parse(line);
startElement(elem); startElement(elem);
} catch (ParseException e) { } catch (ParseException e) {
e.printStackTrace();
//e.printStackTrace();
// invalid lines are sent to the output as information, it might be anything, // invalid lines are sent to the output as information, it might be anything,
task.log(line, Project.MSG_INFO);
task.log(line, Project.MSG_INFO);
} }
} }


@@ -238,16 +247,17 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler {
// if there are elements in the stack we possibly need to close one or // 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 // more elements previous to this one until we got its parent
int indent = elem.getIndent(); int indent = elem.getIndent();
if ( stack.size() > 0 ){
ElementEntry previous = (ElementEntry)stack.peek();
if (stack.size() > 0) {
ElementEntry previous = (ElementEntry) stack.peek();
// close nodes until you got the parent. // close nodes until you got the parent.
try { try {
while ( indent <= previous.getIndent() && stack.size() > 0){
while (indent <= previous.getIndent() && stack.size() > 0) {
stack.pop(); stack.pop();
metricsHandler.endElement("", previous.getType(), previous.getType()); metricsHandler.endElement("", previous.getType(), previous.getType());
previous = (ElementEntry)stack.peek();
previous = (ElementEntry) stack.peek();
} }
} catch (EmptyStackException ignored){}
} catch (EmptyStackException ignored) {
}
} }


// ok, now start the new construct // ok, now start the new construct
@@ -256,7 +266,7 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler {
metricsHandler.startElement("", type, type, attrs); metricsHandler.startElement("", type, type, attrs);


// make sure we keep track of what we did, that's history // make sure we keep track of what we did, that's history
stack.push( new ElementEntry(type, indent) );
stack.push(new ElementEntry(type, indent));
} }


/** /**
@@ -267,36 +277,36 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler {
* @return the type of the metrics element, either PACKAGE, FILE, CLASS or * @return the type of the metrics element, either PACKAGE, FILE, CLASS or
* METHOD. * METHOD.
*/ */
protected String getConstructType(MetricsElement elem){
protected String getConstructType(MetricsElement elem) {
// ok no doubt, it's a file // ok no doubt, it's a file
if ( elem.isCompilationUnit() ){
if (elem.isCompilationUnit()) {
return FILE; return FILE;
} }


// same, we're sure it's a method // same, we're sure it's a method
if ( elem.isMethod() ){
if (elem.isMethod()) {
return METHOD; return METHOD;
} }


// if it's empty, and none of the above it should be a package // if it's empty, and none of the above it should be a package
if ( stack.size() == 0 ){
if (stack.size() == 0) {
return PACKAGE; return PACKAGE;
} }


// ok, this is now black magic time, we will guess the type based on // ok, this is now black magic time, we will guess the type based on
// the previous type and its indent... // the previous type and its indent...
final ElementEntry previous = (ElementEntry)stack.peek();
final ElementEntry previous = (ElementEntry) stack.peek();
final String prevType = previous.getType(); final String prevType = previous.getType();
final int prevIndent = previous.getIndent(); final int prevIndent = previous.getIndent();
final int indent = elem.getIndent(); final int indent = elem.getIndent();
// we're just under a file with a bigger indent so it's a class // we're just under a file with a bigger indent so it's a class
if ( prevType.equals(FILE) && indent > prevIndent ){
if (prevType.equals(FILE) && indent > prevIndent) {
return CLASS; return CLASS;
} }


// we're just under a class with a greater or equals indent, it's a 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) // (there might be several classes in a compilation unit and inner classes as well)
if ( prevType.equals(CLASS) && indent >= prevIndent ){
if (prevType.equals(CLASS) && indent >= prevIndent) {
return CLASS; return CLASS;
} }


@@ -308,17 +318,16 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler {
/** /**
* Create all attributes of a MetricsElement skipping those who have an * Create all attributes of a MetricsElement skipping those who have an
* empty string * empty string
* @param elem
*/ */
protected Attributes createAttributes(MetricsElement elem){
protected Attributes createAttributes(MetricsElement elem) {
AttributesImpl impl = new AttributesImpl(); AttributesImpl impl = new AttributesImpl();
int i = 0; int i = 0;
String name = ATTRIBUTES[i++]; String name = ATTRIBUTES[i++];
impl.addAttribute("", name, name, "CDATA", elem.getName()); impl.addAttribute("", name, name, "CDATA", elem.getName());
Enumeration metrics = elem.getMetrics(); Enumeration metrics = elem.getMetrics();
for (; metrics.hasMoreElements(); i++){
String value = (String)metrics.nextElement();
if ( value.length() > 0 ){
for (; metrics.hasMoreElements(); i++) {
String value = (String) metrics.nextElement();
if (value.length() > 0) {
name = ATTRIBUTES[i]; name = ATTRIBUTES[i];
impl.addAttribute("", name, name, "CDATA", value); impl.addAttribute("", name, name, "CDATA", value);
} }
@@ -333,13 +342,16 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler {
private final static class ElementEntry { private final static class ElementEntry {
private String type; private String type;
private int indent; private int indent;
ElementEntry(String type, int indent){

ElementEntry(String type, int indent) {
this.type = type; this.type = type;
this.indent = indent; this.indent = indent;
} }
public String getType(){

public String getType() {
return type; return type;
} }

public int getIndent() { public int getIndent() {
return indent; return indent;
} }
@@ -351,6 +363,7 @@ class MetricsElement {
private final static NumberFormat METAMATA_NF; private final static NumberFormat METAMATA_NF;


private final static NumberFormat NEUTRAL_NF; private final static NumberFormat NEUTRAL_NF;

static { static {
METAMATA_NF = NumberFormat.getInstance(); METAMATA_NF = NumberFormat.getInstance();
METAMATA_NF.setMaximumFractionDigits(1); METAMATA_NF.setMaximumFractionDigits(1);
@@ -367,30 +380,30 @@ class MetricsElement {


private Vector metrics; private Vector metrics;


MetricsElement(int indent, String construct, Vector metrics){
MetricsElement(int indent, String construct, Vector metrics) {
this.indent = indent; this.indent = indent;
this.construct = construct; this.construct = construct;
this.metrics = metrics; this.metrics = metrics;
} }


public int getIndent(){
public int getIndent() {
return indent; return indent;
} }


public String getName(){
public String getName() {
return construct; return construct;
} }


public Enumeration getMetrics(){
public Enumeration getMetrics() {
return metrics.elements(); return metrics.elements();
} }


public boolean isCompilationUnit(){
return ( construct.endsWith(".java") || construct.endsWith(".class") );
public boolean isCompilationUnit() {
return (construct.endsWith(".java") || construct.endsWith(".class"));
} }


public boolean isMethod(){
return ( construct.endsWith("(...)") || construct.endsWith("()") );
public boolean isMethod() {
return (construct.endsWith("(...)") || construct.endsWith("()"));
} }


public static MetricsElement parse(String line) throws ParseException { public static MetricsElement parse(String line) throws ParseException {
@@ -399,32 +412,32 @@ class MetricsElement {


// i'm using indexOf since I need to know if there are empty strings // i'm using indexOf since I need to know if there are empty strings
// between tabs and I find it easier than with StringTokenizer // between tabs and I find it easier than with StringTokenizer
while ( (pos = line.indexOf('\t')) != -1 ){
while ((pos = line.indexOf('\t')) != -1) {
String token = line.substring(0, pos); String token = line.substring(0, pos);
// only parse what coudl be a valid number. ie not constructs nor no value // only parse what coudl be a valid number. ie not constructs nor no value
/*if (metrics.size() != 0 || token.length() != 0){ /*if (metrics.size() != 0 || token.length() != 0){
Number num = METAMATA_NF.parse(token); // parse with Metamata NF Number num = METAMATA_NF.parse(token); // parse with Metamata NF
token = NEUTRAL_NF.format(num.doubleValue()); // and format with a neutral NF token = NEUTRAL_NF.format(num.doubleValue()); // and format with a neutral NF
}*/ }*/
metrics.addElement( token );
metrics.addElement(token);
line = line.substring(pos + 1); line = line.substring(pos + 1);
} }
metrics.addElement( line );
metrics.addElement(line);


// there should be exactly 14 tokens (1 name + 13 metrics), if not, there is a problem ! // 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);
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 // 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 name, we'll need all this to figure out what type of
// construct it is since we lost all semantics :( // construct it is since we lost all semantics :(
// (#indent[/]*)(#construct.*) // (#indent[/]*)(#construct.*)
String name = (String)metrics.elementAt(0);
String name = (String) metrics.elementAt(0);
metrics.removeElementAt(0); metrics.removeElementAt(0);
int indent = 0; int indent = 0;
pos = name.lastIndexOf('/'); pos = name.lastIndexOf('/');
if (pos != -1){
if (pos != -1) {
name = name.substring(pos + 1); name = name.substring(pos + 1);
indent = pos + 1; // indentation is last position of token + 1 indent = pos + 1; // indentation is last position of token + 1
} }


Loading…
Cancel
Save