Since Metamata was acquired by Webgain, Quality Analyzer 2.0 is also compatible with them. I'm using them for a while and they were requested by Garrick Olson, Garrick.Olson@Aceva.com git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@269423 13f79535-47bb-0310-9956-ffa450edef68master
@@ -0,0 +1,315 @@ | |||
/* | |||
* The Apache Software License, Version 1.1 | |||
* | |||
* Copyright (c) 2000 The Apache Software Foundation. All rights | |||
* reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions | |||
* are met: | |||
* | |||
* 1. Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* 2. Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in | |||
* the documentation and/or other materials provided with the | |||
* distribution. | |||
* | |||
* 3. The end-user documentation included with the redistribution, if | |||
* any, must include the following acknowlegement: | |||
* "This product includes software developed by the | |||
* Apache Software Foundation (http://www.apache.org/)." | |||
* Alternately, this acknowlegement may appear in the software itself, | |||
* if and wherever such third-party acknowlegements normally appear. | |||
* | |||
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||
* Foundation" must not be used to endorse or promote products derived | |||
* from this software without prior written permission. For written | |||
* permission, please contact apache@apache.org. | |||
* | |||
* 5. Products derived from this software may not be called "Apache" | |||
* nor may "Apache" appear in their names without prior written | |||
* permission of the Apache Group. | |||
* | |||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
* SUCH DAMAGE. | |||
* ==================================================================== | |||
* | |||
* This software consists of voluntary contributions made by many | |||
* individuals on behalf of the Apache Software Foundation. For more | |||
* information on the Apache Software Foundation, please see | |||
* <http://www.apache.org/>. | |||
*/ | |||
package org.apache.tools.ant.taskdefs.optional.metamata; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.Task; | |||
import org.apache.tools.ant.taskdefs.*; | |||
import org.apache.tools.ant.types.*; | |||
import org.apache.tools.ant.DirectoryScanner; | |||
import java.io.*; | |||
import java.util.*; | |||
/** | |||
* Somewhat abstract framework to be used for other metama 2.0 tasks. | |||
* This should include, audit, metrics, cover and mparse. | |||
* | |||
* For more information, visit the website at | |||
* <a href="http://www.metamata.com">www.metamata.com</a> | |||
* | |||
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | |||
*/ | |||
public abstract class AbstractMetamataTask extends Task{ | |||
//--------------------------- ATTRIBUTES ----------------------------------- | |||
/** | |||
* The user classpath to be provided. It matches the -classpath of the | |||
* command line. The classpath must includes both the <tt>.class</tt> and the | |||
* <tt>.java</tt> files for accurate audit. | |||
*/ | |||
protected Path classPath = null; | |||
/** the path to the source file */ | |||
protected Path sourcePath = null; | |||
/** | |||
* Metamata home directory. It will be passed as a <tt>metamata.home</tt> property | |||
* and should normally matches the environment property <tt>META_HOME</tt> | |||
* set by the Metamata installer. | |||
*/ | |||
protected File metamataHome = null; | |||
/** the command line used to run MAudit */ | |||
protected CommandlineJava cmdl = new CommandlineJava(); | |||
/** the set of files to be audited */ | |||
protected Vector fileSets = new Vector(); | |||
/** the options file where are stored the command line options */ | |||
protected File optionsFile = null; | |||
// this is used to keep track of which files were included. It will | |||
// be set when calling scanFileSets(); | |||
protected Hashtable includedFiles = null; | |||
public AbstractMetamataTask(){ | |||
} | |||
/** initialize the task with the classname of the task to run */ | |||
protected AbstractMetamataTask(String className) { | |||
cmdl.setVm("java"); | |||
cmdl.setClassname(className); | |||
} | |||
/** the metamata.home property to run all tasks. */ | |||
public void setMetamatahome(final File metamataHome){ | |||
this.metamataHome = metamataHome; | |||
} | |||
/** user classpath */ | |||
public Path createClasspath() { | |||
if (classPath == null) { | |||
classPath = new Path(project); | |||
} | |||
return classPath; | |||
} | |||
/** create the source path for this task */ | |||
public Path createSourcepath(){ | |||
if (sourcePath == null){ | |||
sourcePath = new Path(project); | |||
} | |||
return sourcePath; | |||
} | |||
/** Creates a nested jvmarg element. */ | |||
public Commandline.Argument createJvmarg() { | |||
return cmdl.createVmArgument(); | |||
} | |||
/** -mx or -Xmx depending on VM version */ | |||
public void setMaxmemory(String max){ | |||
if (Project.getJavaVersion().startsWith("1.1")) { | |||
createJvmarg().setValue("-mx" + max); | |||
} else { | |||
createJvmarg().setValue("-Xmx" + max); | |||
} | |||
} | |||
/** The java files or directory to be audited */ | |||
public void addFileSet(FileSet fs) { | |||
fileSets.addElement(fs); | |||
} | |||
/** execute the command line */ | |||
public void execute() throws BuildException { | |||
try { | |||
setUp(); | |||
ExecuteStreamHandler handler = createStreamHandler(); | |||
execute0(handler); | |||
} finally { | |||
cleanUp(); | |||
} | |||
} | |||
//--------------------- PRIVATE/PROTECTED METHODS -------------------------- | |||
/** check the options and build the command line */ | |||
protected void setUp() throws BuildException { | |||
checkOptions(); | |||
// set the classpath as the jar file | |||
File jar = getMetamataJar(metamataHome); | |||
final Path classPath = cmdl.createClasspath(project); | |||
classPath.createPathElement().setLocation(jar); | |||
// set the metamata.home property | |||
final Commandline.Argument vmArgs = cmdl.createVmArgument(); | |||
vmArgs.setValue("-Dmetamata.home=" + metamataHome.getAbsolutePath() ); | |||
// retrieve all the files we want to scan | |||
includedFiles = scanFileSets(); | |||
log(includedFiles.size() + " files added for audit", Project.MSG_VERBOSE); | |||
// write all the options to a temp file and use it ro run the process | |||
Vector options = getOptions(); | |||
optionsFile = createTmpFile(); | |||
generateOptionsFile(optionsFile, options); | |||
Commandline.Argument args = cmdl.createArgument(); | |||
args.setLine("-arguments " + optionsFile.getAbsolutePath()); | |||
} | |||
/** | |||
* create a stream handler that will be used to get the output since | |||
* metamata tools do not report with convenient files such as XML. | |||
*/ | |||
protected abstract ExecuteStreamHandler createStreamHandler(); | |||
/** execute the process with a specific handler */ | |||
protected void execute0(ExecuteStreamHandler handler) throws BuildException { | |||
final Execute process = new Execute(handler); | |||
log(cmdl.toString(), Project.MSG_VERBOSE); | |||
process.setCommandline(cmdl.getCommandline()); | |||
try { | |||
if (process.execute() != 0) { | |||
throw new BuildException("Metamata task failed."); | |||
} | |||
} catch (IOException e){ | |||
throw new BuildException("Failed to launch Metamata task: " + e); | |||
} | |||
} | |||
/** clean up all the mess that we did with temporary objects */ | |||
protected void cleanUp(){ | |||
if (optionsFile != null){ | |||
optionsFile.delete(); | |||
optionsFile = null; | |||
} | |||
} | |||
/** return the location of the jar file used to run */ | |||
protected final File getMetamataJar(File home){ | |||
return new File(home.getAbsoluteFile(), "lib/metamata.jar"); | |||
} | |||
/** validate options set */ | |||
protected void checkOptions() throws BuildException { | |||
// do some validation first | |||
if (metamataHome == null || !metamataHome.exists()){ | |||
throw new BuildException("'metamatahome' must point to Metamata home directory."); | |||
} | |||
metamataHome = project.resolveFile(metamataHome.getPath()); | |||
File jar = getMetamataJar(metamataHome); | |||
if (!jar.exists()){ | |||
throw new BuildException( jar + " does not exist. Check your metamata installation."); | |||
} | |||
} | |||
/** return all options of the command line as string elements */ | |||
protected abstract Vector getOptions(); | |||
protected void generateOptionsFile(File tofile, Vector options) throws BuildException { | |||
FileWriter fw = null; | |||
try { | |||
fw = new FileWriter(tofile); | |||
PrintWriter pw = new PrintWriter(fw); | |||
final int size = options.size(); | |||
for (int i = 0; i < size; i++){ | |||
pw.println( options.elementAt(i) ); | |||
} | |||
pw.flush(); | |||
} catch (IOException e){ | |||
throw new BuildException("Error while writing options file " + tofile, e); | |||
} finally { | |||
if (fw != null){ | |||
try { | |||
fw.close(); | |||
} catch (IOException ignored){} | |||
} | |||
} | |||
} | |||
protected Hashtable getFileMapping(){ | |||
return includedFiles; | |||
} | |||
/** | |||
* convenient method for JDK 1.1. Will copy all elements from src to dest | |||
*/ | |||
protected static final void addAllVector(Vector dest, Enumeration files){ | |||
while (files.hasMoreElements()) { | |||
dest.addElement( files.nextElement() ); | |||
} | |||
} | |||
protected final static File createTmpFile(){ | |||
// must be compatible with JDK 1.1 !!!! | |||
final long rand = (new Random(System.currentTimeMillis())).nextLong(); | |||
File file = new File("metamata" + rand + ".tmp"); | |||
return file; | |||
} | |||
/** | |||
* @return the list of .java files (as their absolute path) that should | |||
* be audited. | |||
*/ | |||
protected Hashtable scanFileSets(){ | |||
Hashtable files = new Hashtable(); | |||
for (int i = 0; i < fileSets.size(); i++){ | |||
FileSet fs = (FileSet) fileSets.elementAt(i); | |||
DirectoryScanner ds = fs.getDirectoryScanner(project); | |||
ds.scan(); | |||
String[] f = ds.getIncludedFiles(); | |||
log(i + ") Adding " + f.length + " files from directory " + ds.getBasedir(), Project.MSG_VERBOSE); | |||
for (int j = 0; j < f.length; j++){ | |||
String pathname = f[j]; | |||
if ( pathname.endsWith(".java") ){ | |||
File file = new File( ds.getBasedir(), pathname); | |||
// file = project.resolveFile(file.getAbsolutePath()); | |||
String classname = pathname.substring(0, pathname.length()-".java".length()); | |||
classname = classname.replace(File.separatorChar, '.'); | |||
files.put( file.getAbsolutePath(), classname ); // it's a java file, add it. | |||
} | |||
} | |||
} | |||
return files; | |||
} | |||
} |
@@ -0,0 +1,255 @@ | |||
/* | |||
* The Apache Software License, Version 1.1 | |||
* | |||
* Copyright (c) 2000 The Apache Software Foundation. All rights | |||
* reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions | |||
* are met: | |||
* | |||
* 1. Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* 2. Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in | |||
* the documentation and/or other materials provided with the | |||
* distribution. | |||
* | |||
* 3. The end-user documentation included with the redistribution, if | |||
* any, must include the following acknowlegement: | |||
* "This product includes software developed by the | |||
* Apache Software Foundation (http://www.apache.org/)." | |||
* Alternately, this acknowlegement may appear in the software itself, | |||
* if and wherever such third-party acknowlegements normally appear. | |||
* | |||
* 4. The names "The Jakarta Project", "Ant", and "Apache Software | |||
* Foundation" must not be used to endorse or promote products derived | |||
* from this software without prior written permission. For written | |||
* permission, please contact apache@apache.org. | |||
* | |||
* 5. Products derived from this software may not be called "Apache" | |||
* nor may "Apache" appear in their names without prior written | |||
* permission of the Apache Group. | |||
* | |||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
* SUCH DAMAGE. | |||
* ==================================================================== | |||
* | |||
* This software consists of voluntary contributions made by many | |||
* individuals on behalf of the Apache Software Foundation. For more | |||
* information on the Apache Software Foundation, please see | |||
* <http://www.apache.org/>. | |||
*/ | |||
package org.apache.tools.ant.taskdefs.optional.metamata; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.Task; | |||
import org.apache.tools.ant.taskdefs.*; | |||
import org.apache.tools.ant.types.*; | |||
import org.apache.tools.ant.DirectoryScanner; | |||
import org.apache.tools.ant.util.regexp.*; | |||
import java.io.*; | |||
import java.util.*; | |||
/** | |||
* Metamata Audit evaluates Java code for programming errors, weaknesses, and | |||
* style violation. | |||
* <p> | |||
* Metamata Audit exists in three versions: | |||
* <ul> | |||
* <li>The Lite version evaluates about 15 built-in rules.</li> | |||
* <li>The Pro version evaluates about 50 built-in rules.</li> | |||
* <li>The Enterprise version allows you to add your own customized rules via the API.</li> | |||
* <ul> | |||
* For more information, visit the website at | |||
* <a href="http://www.metamata.com">www.metamata.com</a> | |||
* | |||
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | |||
*/ | |||
public class MAudit extends AbstractMetamataTask { | |||
/* As of Metamata 2.0, the command line of MAudit is as follows: | |||
Usage | |||
maudit <option>... <path>... [-unused <search-path>...] | |||
Parameters | |||
path File or directory to audit. | |||
search-path File or directory to search for declaration uses. | |||
Options | |||
-arguments -A <file> Includes command line arguments from file. | |||
-classpath -cp <path> Sets class path (also source path unless one | |||
explicitly set). Overrides METAPATH/CLASSPATH. | |||
-exit -x Exits after the first error. | |||
-fix -f Automatically fixes certain errors. | |||
-fullpath Prints full path for locations. | |||
-help -h Prints help and exits. | |||
-list -l Creates listing file for each audited file. | |||
-offsets -off Offset and length for locations. | |||
-output -o <file> Prints output to file. | |||
-quiet -q Suppresses copyright and summary messages. | |||
-sourcepath <path> Sets source path. Overrides SOURCEPATH. | |||
-tab -t Prints a tab character after first argument. | |||
-unused -u Finds declarations unused in search paths. | |||
-verbose -v Prints all messages. | |||
-version -V Prints version and exits. | |||
*/ | |||
//---------------------- PUBLIC METHODS ------------------------------------ | |||
/** pattern used by maudit to report the error for a file */ | |||
/** RE does not seems to support regexp pattern with comments so i'm stripping it*/ | |||
// (?:file:)?((?#filepath).+):((?#line)\\d+)\\s*:\\s+((?#message).*) | |||
static final String AUDIT_PATTERN = "(?:file:)?(.+):(\\d+)\\s*:\\s+(.*)"; | |||
protected File outFile = null; | |||
protected Path searchPath = null; | |||
protected boolean fix = false; | |||
protected boolean list = false; | |||
protected boolean unused = false; | |||
/** default constructor */ | |||
public MAudit() { | |||
super("com.metamata.gui.rc.MAudit"); | |||
} | |||
/** set the destination file which should be an xml file */ | |||
public void setTofile(File outFile){ | |||
this.outFile = outFile; | |||
} | |||
public void setFix(boolean flag){ | |||
this.fix = flag; | |||
} | |||
public void setList(boolean flag){ | |||
this.list = flag; | |||
} | |||
public void setUnused(boolean flag){ | |||
this.unused = flag; | |||
} | |||
public Path createSearchpath(){ | |||
if (searchPath == null){ | |||
searchPath = new Path(project); | |||
} | |||
return searchPath; | |||
} | |||
protected Vector getOptions(){ | |||
Vector options = new Vector(512); | |||
// there is a bug in Metamata 2.0 build 37. The sourcepath argument does | |||
// not work. So we will use the sourcepath prepended to classpath. (order | |||
// is important since Metamata looks at .class and .java) | |||
if (sourcePath != null){ | |||
sourcePath.append(classPath); // srcpath is prepended | |||
classPath = sourcePath; | |||
sourcePath = null; // prevent from using -sourcepath | |||
} | |||
// don't forget to modify the pattern if you change the options reporting | |||
if (classPath != null){ | |||
options.addElement("-classpath"); | |||
options.addElement(classPath.toString()); | |||
} | |||
// suppress copyright msg when running, we will let it so that this | |||
// will be the only output to the console if in xml mode | |||
// options.addElement("-quiet"); | |||
if (fix){ | |||
options.addElement("-fix"); | |||
} | |||
options.addElement("-fullpath"); | |||
// generate .maudit files much more detailed than the report | |||
// I don't like it very much, I think it could be interesting | |||
// to get all .maudit files and include them in the XML. | |||
if (list){ | |||
options.addElement("-list"); | |||
} | |||
if (sourcePath != null){ | |||
options.addElement("-sourcepath"); | |||
options.addElement(sourcePath.toString()); | |||
} | |||
if (unused){ | |||
options.addElement("-unused"); | |||
options.addElement(searchPath.toString()); | |||
} | |||
addAllVector(options, includedFiles.keys()); | |||
return options; | |||
} | |||
protected void checkOptions() throws BuildException { | |||
super.checkOptions(); | |||
if (unused && searchPath == null){ | |||
throw new BuildException("'searchpath' element must be set when looking for 'unused' declarations."); | |||
} | |||
if (!unused && searchPath != null){ | |||
log("'searchpath' element ignored. 'unused' attribute is disabled.", Project.MSG_WARN); | |||
} | |||
} | |||
protected ExecuteStreamHandler createStreamHandler() throws BuildException { | |||
ExecuteStreamHandler handler = null; | |||
// if we didn't specify a file, then use a screen report | |||
if (outFile == null){ | |||
handler = new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_INFO); | |||
} else { | |||
try { | |||
//XXX | |||
OutputStream out = new FileOutputStream( outFile ); | |||
handler = new MAuditStreamHandler(this, out); | |||
} catch (IOException e){ | |||
throw new BuildException(e); | |||
} | |||
} | |||
return handler; | |||
} | |||
protected void cleanUp() throws BuildException { | |||
super.cleanUp(); | |||
// at this point if -list is used, we should move | |||
// the .maudit file since we cannot choose their location :( | |||
// the .maudit files match the .java files | |||
// we'll use includedFiles to get the .maudit files. | |||
/*if (out != null){ | |||
// close it if not closed by the handler... | |||
}*/ | |||
} | |||
/** the inner class used to report violation information */ | |||
static final class Violation { | |||
int line; | |||
String error; | |||
} | |||
/** handy factory to create a violation */ | |||
static final Violation createViolation(int line, String msg){ | |||
Violation violation = new Violation(); | |||
violation.line = line; | |||
violation.error = msg; | |||
return violation; | |||
} | |||
} | |||
@@ -0,0 +1,241 @@ | |||
/* | |||
* The Apache Software License, Version 1.1 | |||
* | |||
* Copyright (c) 2000 The Apache Software Foundation. All rights | |||
* reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions | |||
* are met: | |||
* | |||
* 1. Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* 2. Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in | |||
* the documentation and/or other materials provided with the | |||
* distribution. | |||
* | |||
* 3. The end-user documentation included with the redistribution, if | |||
* any, must include the following acknowlegement: | |||
* "This product includes software developed by the | |||
* Apache Software Foundation (http://www.apache.org/)." | |||
* Alternately, this acknowlegement may appear in the software itself, | |||
* if and wherever such third-party acknowlegements normally appear. | |||
* | |||
* 4. The names "The Jakarta Project", "Ant", and "Apache Software | |||
* Foundation" must not be used to endorse or promote products derived | |||
* from this software without prior written permission. For written | |||
* permission, please contact apache@apache.org. | |||
* | |||
* 5. Products derived from this software may not be called "Apache" | |||
* nor may "Apache" appear in their names without prior written | |||
* permission of the Apache Group. | |||
* | |||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
* SUCH DAMAGE. | |||
* ==================================================================== | |||
* | |||
* This software consists of voluntary contributions made by many | |||
* individuals on behalf of the Apache Software Foundation. For more | |||
* information on the Apache Software Foundation, please see | |||
* <http://www.apache.org/>. | |||
*/ | |||
package org.apache.tools.ant.taskdefs.optional.metamata; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.Task; | |||
import org.apache.tools.ant.taskdefs.*; | |||
import org.apache.tools.ant.types.*; | |||
import org.apache.tools.ant.util.regexp.*; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.util.DOMElementWriter; | |||
import org.w3c.dom.*; | |||
import java.io.*; | |||
import java.util.*; | |||
import javax.xml.parsers.*; | |||
/** | |||
* This is a very bad stream handler for the MAudit task. | |||
* All report to stdout that does not match a specific report pattern is dumped | |||
* to the Ant output as warn level. The report that match the pattern is stored | |||
* in a map with the key being the filepath that caused the error report. | |||
* <p> | |||
* The limitation with the choosen implementation is clear: | |||
* <ul> | |||
* <li>it does not handle multiline report( message that has \n ). the part until | |||
* the \n will be stored and the other part (which will not match the pattern) | |||
* will go to Ant output in Warn level. | |||
* <li>it does not report error that goes to stderr. | |||
* </ul> | |||
* | |||
* @author <a href="sbailliez@imediation.com">Stephane Bailliez</a> | |||
*/ | |||
class MAuditStreamHandler implements ExecuteStreamHandler { | |||
protected MAudit task; | |||
/** reader for stdout */ | |||
protected BufferedReader br; | |||
/** matcher that will be used to extract the info from the line */ | |||
protected RegexpMatcher matcher; | |||
/** | |||
* this is where the XML output will go, should mostly be a file | |||
* the caller is responsible for flushing and closing this stream | |||
*/ | |||
protected OutputStream xmlOut = null; | |||
/** | |||
* the multimap. The key in the map is the filepath that caused the audit | |||
* error and the value is a vector of MAudit.Violation entries. | |||
*/ | |||
protected Hashtable auditedFiles = new Hashtable(); | |||
MAuditStreamHandler(MAudit task, OutputStream xmlOut){ | |||
this.task = task; | |||
this.xmlOut = xmlOut; | |||
/** the matcher should be the Oro one. I don't know about the other one */ | |||
matcher = (new RegexpMatcherFactory()).newRegexpMatcher(); | |||
matcher.setPattern(MAudit.AUDIT_PATTERN); | |||
} | |||
/** Ignore. */ | |||
public void setProcessInputStream(OutputStream os) {} | |||
/** Ignore. */ | |||
public void setProcessErrorStream(InputStream is) {} | |||
/** Set the inputstream */ | |||
public void setProcessOutputStream(InputStream is) throws IOException { | |||
br = new BufferedReader(new InputStreamReader(is)); | |||
} | |||
/** Invokes parseOutput. This will block until the end :-(*/ | |||
public void start() throws IOException { | |||
parseOutput(br); | |||
} | |||
/** | |||
* Pretty dangerous business here. It serializes what was extracted from | |||
* the MAudit output and write it to the output. | |||
*/ | |||
public void stop() { | |||
// serialize the content as XML, move this to another method | |||
// this is the only code that could be needed to be overrided | |||
Document doc = getDocumentBuilder().newDocument(); | |||
Element rootElement = doc.createElement("classes"); | |||
Enumeration keys = auditedFiles.keys(); | |||
Hashtable filemapping = task.getFileMapping(); | |||
rootElement.setAttribute("audited", String.valueOf(filemapping.size())); | |||
rootElement.setAttribute("reported", String.valueOf(auditedFiles.size())); | |||
int errors = 0; | |||
while (keys.hasMoreElements()){ | |||
String filepath = (String)keys.nextElement(); | |||
Vector v = (Vector)auditedFiles.get(filepath); | |||
String fullclassname = (String)filemapping.get(filepath); | |||
if (fullclassname == null) { | |||
task.getProject().log("Could not find class mapping for " + filepath, Project.MSG_WARN); | |||
continue; | |||
} | |||
int pos = fullclassname.lastIndexOf('.'); | |||
String pkg = (pos == -1) ? "" : fullclassname.substring(0, pos); | |||
String clazzname = (pos == -1) ? fullclassname : fullclassname.substring(pos + 1); | |||
Element clazz = doc.createElement("class"); | |||
clazz.setAttribute("package", pkg); | |||
clazz.setAttribute("name", clazzname); | |||
clazz.setAttribute("violations", String.valueOf(v.size())); | |||
errors += v.size(); | |||
for (int i = 0; i < v.size(); i++){ | |||
MAudit.Violation violation = (MAudit.Violation)v.elementAt(i); | |||
Element error = doc.createElement("violation"); | |||
error.setAttribute("line", String.valueOf(violation.line)); | |||
error.setAttribute("message", violation.error); | |||
clazz.appendChild(error); | |||
} | |||
rootElement.appendChild(clazz); | |||
} | |||
rootElement.setAttribute("violations", String.valueOf(errors)); | |||
// now write it to the outputstream, not very nice code | |||
if (xmlOut != null) { | |||
Writer wri = null; | |||
try { | |||
wri = new OutputStreamWriter(xmlOut, "UTF-8"); | |||
wri.write("<?xml version=\"1.0\"?>\n"); | |||
(new DOMElementWriter()).write(rootElement, wri, 0, " "); | |||
wri.flush(); | |||
} catch(IOException exc) { | |||
task.log("Unable to write log file", Project.MSG_ERR); | |||
} finally { | |||
if (xmlOut != System.out && xmlOut != System.err) { | |||
if (wri != null) { | |||
try { | |||
wri.close(); | |||
} catch (IOException e) {} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
protected static DocumentBuilder getDocumentBuilder() { | |||
try { | |||
return DocumentBuilderFactory.newInstance().newDocumentBuilder(); | |||
} | |||
catch(Exception exc) { | |||
throw new ExceptionInInitializerError(exc); | |||
} | |||
} | |||
/** read each line and process it */ | |||
protected void parseOutput(BufferedReader br) throws IOException { | |||
String line = null; | |||
while ( (line = br.readLine()) != null ){ | |||
processLine(line); | |||
} | |||
} | |||
// we suppose here that there is only one report / line. | |||
// There will obviouslly be a problem if the message is on several lines... | |||
protected void processLine(String line){ | |||
Vector matches = matcher.getGroups(line); | |||
if (matches != null) { | |||
String file = (String)matches.elementAt(1); | |||
int lineNum = Integer.parseInt((String)matches.elementAt(2)); | |||
String msg = (String)matches.elementAt(3); | |||
addViolationEntry(file, MAudit.createViolation(lineNum, msg) ); | |||
} else { | |||
// this doesn't match..report it as info, it could be | |||
// either the copyright, summary or a multiline message (damn !) | |||
task.log(line, Project.MSG_INFO); | |||
} | |||
} | |||
/** add a violation entry for the file */ | |||
protected void addViolationEntry(String file, MAudit.Violation entry){ | |||
Vector violations = (Vector)auditedFiles.get(file); | |||
// if there is no decl for this file yet, create it. | |||
if (violations == null){ | |||
violations = new Vector(); | |||
auditedFiles.put(file, violations); | |||
} | |||
violations.add( entry ); | |||
} | |||
} |
@@ -0,0 +1,291 @@ | |||
/* | |||
* The Apache Software License, Version 1.1 | |||
* | |||
* Copyright (c) 2000 The Apache Software Foundation. All rights | |||
* reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions | |||
* are met: | |||
* | |||
* 1. Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* 2. Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in | |||
* the documentation and/or other materials provided with the | |||
* distribution. | |||
* | |||
* 3. The end-user documentation included with the redistribution, if | |||
* any, must include the following acknowlegement: | |||
* "This product includes software developed by the | |||
* Apache Software Foundation (http://www.apache.org/)." | |||
* Alternately, this acknowlegement may appear in the software itself, | |||
* if and wherever such third-party acknowlegements normally appear. | |||
* | |||
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||
* Foundation" must not be used to endorse or promote products derived | |||
* from this software without prior written permission. For written | |||
* permission, please contact apache@apache.org. | |||
* | |||
* 5. Products derived from this software may not be called "Apache" | |||
* nor may "Apache" appear in their names without prior written | |||
* permission of the Apache Group. | |||
* | |||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
* SUCH DAMAGE. | |||
* ==================================================================== | |||
* | |||
* This software consists of voluntary contributions made by many | |||
* individuals on behalf of the Apache Software Foundation. For more | |||
* information on the Apache Software Foundation, please see | |||
* <http://www.apache.org/>. | |||
*/ | |||
package org.apache.tools.ant.taskdefs.optional.metamata; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.Task; | |||
import org.apache.tools.ant.taskdefs.*; | |||
import org.apache.tools.ant.types.*; | |||
import java.io.*; | |||
import java.util.*; | |||
/** | |||
* Calculates global complexity and quality metrics on Java source code. | |||
* | |||
* You will not be able to use this task with the evaluation version since | |||
* as of Metamata 2.0, Metrics does not support command line :-( | |||
* | |||
* For more information, visit the website at | |||
* <a href="http://www.metamata.com">www.metamata.com</a> | |||
* | |||
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a> | |||
*/ | |||
public class MMetrics extends AbstractMetamataTask { | |||
/* | |||
The command line options as of Metamata 2.0 are as follows: | |||
Usage | |||
mmetrics <option>... <path>... | |||
Parameters | |||
path File or directory to measure. | |||
Options | |||
-arguments -A <file> Includes command line arguments from file. | |||
-classpath -cp <path> Sets class path (also source path unless one | |||
explicitly set). Overrides METAPATH/CLASSPATH. | |||
-compilation-units Measure compilation units. | |||
-files Measure compilation units. | |||
-format -f <format> Sets output format, default output file type. | |||
-help -h Prints help and exits. | |||
-indent -i <string> Sets string used to indent labels one level. | |||
-methods Measure methods, types, and compilation units. | |||
-output -o <file> Sets output file name. | |||
-quiet -q Suppresses copyright message. | |||
-sourcepath <path> Sets source path. Overrides SOURCEPATH. | |||
-types Measure types and compilation units. | |||
-verbose -v Prints all messages. | |||
-version -V Prints version and exits. | |||
Format Options | |||
comma csv Format output as comma-separated text. | |||
html htm Format output as an HTML table. | |||
tab tab-separated tsv Format output as tab-separated text. | |||
text txt Format output as space-aligned text. | |||
*/ | |||
/** the granularity mode. Should be one of 'files', 'methods' and 'types'. */ | |||
protected String granularity = null; | |||
/** the XML output file */ | |||
protected File outFile = null; | |||
/** the location of the temporary txt report */ | |||
protected File tmpFile = createTmpFile(); | |||
protected Path path = null; | |||
//--------------------------- PUBLIC METHODS ------------------------------- | |||
/** default constructor */ | |||
public MMetrics() { | |||
super("com.metamata.sc.MMetrics"); | |||
} | |||
/** | |||
* set the granularity of the audit. Should be one of 'files', 'methods' | |||
* or 'types'. | |||
* @param granularity the audit reporting mode. | |||
*/ | |||
public void setGranularity(String granularity){ | |||
this.granularity = granularity; | |||
} | |||
/** | |||
* Set the output XML file | |||
* @param file the xml file to write the XML report to. | |||
*/ | |||
public void setTofile(File file){ | |||
this.outFile = file; | |||
} | |||
/** | |||
* Set a new path (directory) to measure metrics from. | |||
* @return the path instance to use. | |||
*/ | |||
public Path createPath(){ | |||
if (path == null) { | |||
path = new Path(project); | |||
} | |||
return path; | |||
} | |||
//------------------- PROTECTED / PRIVATE METHODS -------------------------- | |||
// check for existing options and outfile, all other are optional | |||
protected void checkOptions() throws BuildException { | |||
super.checkOptions(); | |||
if ( !"files".equals(granularity) && !"methods".equals(granularity) | |||
&& !"types".equals(granularity) ){ | |||
throw new BuildException("Metrics reporting granularity is invalid. Must be one of 'files', 'methods', 'types'"); | |||
} | |||
if (outFile == null){ | |||
throw new BuildException("Output XML file must be set via 'tofile' attribute."); | |||
} | |||
if (path == null && fileSets.size() == 0){ | |||
throw new BuildException("Must set either paths (path element) or files (fileset element)"); | |||
} | |||
// I don't accept dirs and files at the same time, I cannot recognize the semantic in the result | |||
if (path != null && fileSets.size() > 0){ | |||
throw new BuildException("Cannot set paths (path element) and files (fileset element) at the same time"); | |||
} | |||
} | |||
protected void execute0(ExecuteStreamHandler handler) throws BuildException { | |||
super.execute0(handler); | |||
transformFile(); | |||
} | |||
/** | |||
* transform the generated file via the handler | |||
* This function can either be called if the result is written to the output | |||
* file via -output or we could use the handler directly on stdout if not. | |||
* @see #createStreamHandler() | |||
*/ | |||
protected void transformFile() throws BuildException { | |||
FileInputStream tmpStream = null; | |||
try { | |||
tmpStream = new FileInputStream( tmpFile ); | |||
} catch (IOException e){ | |||
throw new BuildException("Error reading temporary file: " + tmpFile, e); | |||
} | |||
FileOutputStream xmlStream = null; | |||
try { | |||
xmlStream = new FileOutputStream(outFile); | |||
ExecuteStreamHandler xmlHandler = new MMetricsStreamHandler(this, xmlStream); | |||
xmlHandler.setProcessOutputStream(tmpStream); | |||
xmlHandler.start(); | |||
xmlHandler.stop(); | |||
} catch (IOException e){ | |||
throw new BuildException("Error creating output file: " + outFile, e); | |||
} finally { | |||
if (xmlStream != null){ | |||
try { | |||
xmlStream.close(); | |||
} catch (IOException ignored){} | |||
} | |||
if (tmpStream != null){ | |||
try { | |||
tmpStream.close(); | |||
} catch (IOException ignored){} | |||
} | |||
} | |||
} | |||
/** cleanup the temporary txt report */ | |||
protected void cleanUp() throws BuildException { | |||
try { | |||
super.cleanUp(); | |||
} finally { | |||
if (tmpFile != null){ | |||
tmpFile.delete(); | |||
tmpFile = null; | |||
} | |||
} | |||
} | |||
/** | |||
* if the report is transform via a temporary txt file we should use a | |||
* a normal logger here, otherwise we could use the metrics handler | |||
* directly to capture and transform the output on stdout to XML. | |||
*/ | |||
protected ExecuteStreamHandler createStreamHandler(){ | |||
// write the report directtly to an XML stream | |||
// return new MMetricsStreamHandler(this, xmlStream); | |||
return new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_INFO); | |||
} | |||
protected Vector getOptions(){ | |||
Vector options = new Vector(512); | |||
// there is a bug in Metamata 2.0 build 37. The sourcepath argument does | |||
// not work. So we will use the sourcepath prepended to classpath. (order | |||
// is important since Metamata looks at .class and .java) | |||
if (sourcePath != null){ | |||
sourcePath.append(classPath); // srcpath is prepended | |||
classPath = sourcePath; | |||
sourcePath = null; // prevent from using -sourcepath | |||
} | |||
// don't forget to modify the pattern if you change the options reporting | |||
if (classPath != null){ | |||
options.addElement("-classpath"); | |||
options.addElement(classPath); | |||
} | |||
options.addElement( "-output" ); | |||
options.addElement( tmpFile.toString() ); | |||
options.addElement( "-" + granularity); | |||
// display the metamata copyright | |||
// options.addElement( "-quiet"); | |||
options.addElement( "-format"); | |||
// need this because that's what the handler is using, it's | |||
// way easier to process than any other separator | |||
options.addElement( "tab"); | |||
// specify a / as the indent character, used by the handler. | |||
options.addElement( "-i"); | |||
options.addElement( "/"); | |||
// directories | |||
String[] dirs = path.list(); | |||
for (int i = 0; i < dirs.length; i++){ | |||
options.addElement( dirs[i] ); | |||
} | |||
// files next. | |||
addAllVector(options, includedFiles.keys()); | |||
return options; | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||