diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java b/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java index 667fda3f0..cd6ba582a 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java @@ -1,7 +1,7 @@ /* * 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. * * Redistribution and use in source and binary forms, with or without @@ -53,25 +53,25 @@ */ 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.DirectoryScanner; import org.apache.tools.ant.Project; 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.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.CommandlineJava; 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. @@ -80,11 +80,9 @@ import java.util.Random; * For more information, visit the website at * www.metamata.com * - * @author Stephane Bailliez + * @author Stephane Bailliez */ -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 @@ -116,7 +114,7 @@ public abstract class AbstractMetamataTask extends Task{ // be set when calling scanFileSets(); protected Hashtable includedFiles = null; - public AbstractMetamataTask(){ + public AbstractMetamataTask() { } /** 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. */ - 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 */ @@ -139,8 +141,8 @@ public abstract class AbstractMetamataTask extends Task{ } /** create the source path for this task */ - public Path createSourcepath(){ - if (sourcePath == null){ + public Path createSourcepath() { + if (sourcePath == null) { sourcePath = new Path(project); } return sourcePath; @@ -152,7 +154,7 @@ public abstract class AbstractMetamataTask extends Task{ } /** -mx or -Xmx depending on VM version */ - public void setMaxmemory(String max){ + public void setMaxmemory(String max) { if (Project.getJavaVersion().startsWith("1.1")) { createJvmarg().setValue("-mx" + max); } else { @@ -177,8 +179,6 @@ public abstract class AbstractMetamataTask extends Task{ } } - //--------------------- PRIVATE/PROTECTED METHODS -------------------------- - /** check the options and build the command line */ protected void setUp() throws BuildException { checkOptions(); @@ -190,10 +190,12 @@ public abstract class AbstractMetamataTask extends Task{ // set the metamata.home property 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 - 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); // 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) { 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 */ - protected void cleanUp(){ - if (optionsFile != null){ + 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(new File(home.getAbsolutePath()), "lib/metamata.jar"); + protected final File getMetamataJar(File home) { + return new File(home, "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."); + if (metamataHome == null || !metamataHome.exists()) { + throw new BuildException("'home' 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."); + 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); PrintWriter pw = new PrintWriter(fw); 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(); - } catch (IOException e){ + } catch (IOException e) { throw new BuildException("Error while writing options file " + tofile, e); } finally { - if (fw != null){ + if (fw != null) { try { fw.close(); - } catch (IOException ignored){} + } catch (IOException ignored) { + } } } } - protected Hashtable getFileMapping(){ + 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){ + protected static final void addAllVector(Vector dest, Enumeration files) { 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 * be audited. */ - protected Hashtable scanFileSets(){ + + protected Hashtable scanSources(Hashtable map) { 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); 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++){ + for (int j = 0; j < f.length; 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()); - String classname = pathname.substring(0, pathname.length()-".java".length()); + 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. + files.put(file.getAbsolutePath(), classname); // it's a java file, add it. } } } 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; + } + } diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java b/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java index bacb00005..1e2caac38 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java @@ -1,7 +1,7 @@ /* * 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. * * Redistribution and use in source and binary forms, with or without @@ -53,21 +53,19 @@ */ 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.Project; - import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.FileSet; 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 * style violation. @@ -81,7 +79,7 @@ import java.util.Vector; * For more information, visit the website at * www.metamata.com * - * @author Stephane Bailliez + * @author Stephane Bailliez */ public class MAudit extends AbstractMetamataTask { @@ -119,15 +117,24 @@ public class MAudit extends AbstractMetamataTask { // (?:file:)?((?#filepath).+):((?#line)\\d+)\\s*:\\s+((?#message).*) 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 */ public MAudit() { @@ -135,49 +142,101 @@ public class MAudit extends AbstractMetamataTask { } /** set the destination file which should be an xml file */ - public void setTofile(File outFile){ + public void setTofile(File outFile) { this.outFile = outFile; } - public void setFix(boolean flag){ + public void setFix(boolean flag) { this.fix = flag; } - public void setList(boolean flag){ + public void setList(boolean flag) { this.list = flag; } - public void setUnused(boolean flag){ + public void setUnused(boolean 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; } - protected Vector getOptions(){ + protected Vector getOptions() { 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 // not work. So we will use the sourcepath prepended to classpath. (order // is important since Metamata looks at .class and .java) - if (sourcePath != null){ + 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){ + 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){ + 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("-fullpath"); @@ -185,43 +244,45 @@ public class MAudit extends AbstractMetamataTask { // 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){ + if (list) { options.addElement("-list"); } - if (sourcePath != null){ + if (sourcePath != null) { options.addElement("-sourcepath"); options.addElement(sourcePath.toString()); } - - if (unused){ + addAllVector(options, includedFiles.keys()); + 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){ + if (unused && searchPath == null) { 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); } + if (rulesPath != null) { + cmdl.createClasspath(getProject()).addExisting(rulesPath); + } } - + 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); + if (outFile == null) { + handler = new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_INFO); } else { try { //XXX - OutputStream out = new FileOutputStream( outFile ); + OutputStream out = new FileOutputStream(outFile); handler = new MAuditStreamHandler(this, out); - } catch (IOException e){ + } catch (IOException e) { throw new BuildException(e); } } @@ -242,18 +303,9 @@ public class MAudit extends AbstractMetamataTask { /** the inner class used to report violation information */ final static class Violation { - int line; + String 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; - } - } - diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java b/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java index 0bdf4481e..f6fdc367c 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java @@ -1,7 +1,7 @@ /* * 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. * * Redistribution and use in source and binary forms, with or without @@ -53,31 +53,34 @@ */ 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.OutputStream; -import java.io.InputStream; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Writer; +import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.util.Hashtable; +import java.io.Writer; +import java.util.Date; import java.util.Enumeration; +import java.util.Hashtable; import java.util.Vector; import javax.xml.parsers.DocumentBuilder; 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. @@ -93,31 +96,41 @@ import javax.xml.parsers.DocumentBuilderFactory; *
  • it does not report error that goes to stderr. * * - * @author Stephane Bailliez + * @author Stephane Bailliez */ class MAuditStreamHandler implements ExecuteStreamHandler { - protected MAudit task; + /** parent task */ + private MAudit task; /** reader for stdout */ - protected BufferedReader br; + private BufferedReader br; /** 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 * 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 * 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.xmlOut = xmlOut; /** the matcher should be the Oro one. I don't know about the other one */ @@ -126,10 +139,14 @@ class MAuditStreamHandler implements ExecuteStreamHandler { } /** Ignore. */ - public void setProcessInputStream(OutputStream os) {} + public void setProcessInputStream(OutputStream os) { + } /** 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 */ public void setProcessOutputStream(InputStream is) throws IOException { @@ -138,6 +155,8 @@ class MAuditStreamHandler implements ExecuteStreamHandler { /** Invokes parseOutput. This will block until the end :-(*/ public void start() throws IOException { + program_start = new Date(); + errThread.start(); parseOutput(br); } @@ -146,19 +165,32 @@ class MAuditStreamHandler implements ExecuteStreamHandler { * the MAudit output and write it to the output. */ 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 - // 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(); Element rootElement = doc.createElement("classes"); Enumeration keys = auditedFiles.keys(); 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("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); + 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; @@ -169,12 +201,13 @@ class MAuditStreamHandler implements ExecuteStreamHandler { 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); + 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"); - error.setAttribute("line", String.valueOf(violation.line)); + error.setAttribute("line", violation.line); error.setAttribute("message", violation.error); clazz.appendChild(error); } @@ -183,54 +216,67 @@ class MAuditStreamHandler implements ExecuteStreamHandler { 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("\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("\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() { try { return DocumentBuilderFactory.newInstance().newDocumentBuilder(); - } - catch(Exception exc) { + } catch (Exception 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 */ protected void parseOutput(BufferedReader br) throws IOException { String line = null; - while ( (line = br.readLine()) != 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){ + 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) ); + 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 { // this doesn't match..report it as info, it could be // either the copyright, summary or a multiline message (damn !) @@ -239,14 +285,14 @@ class MAuditStreamHandler implements ExecuteStreamHandler { } /** 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); } } diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java b/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java index d5f6bdb11..e739d7ccb 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java @@ -53,21 +53,19 @@ */ 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.Project; - import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.EnumeratedAttribute; 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. * @@ -114,15 +112,15 @@ Format Options */ /** the granularity mode. Should be one of 'files', 'methods' and 'types'. */ - protected String granularity = null; + private String granularity = null; /** the XML output file */ - protected File outFile = null; - + private File outFile = null; + /** the location of the temporary txt report */ - protected File tmpFile = createTmpFile(); + private File tmpFile; - protected Path path = null; + private Path path = null; //--------------------------- PUBLIC METHODS ------------------------------- @@ -131,20 +129,29 @@ Format Options 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' * or 'types'. * @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 * @param file the xml file to write the XML report to. */ - public void setTofile(File file){ + public void setTofile(File file) { this.outFile = file; } @@ -152,7 +159,7 @@ Format Options * Set a new path (directory) to measure metrics from. * @return the path instance to use. */ - public Path createPath(){ + public Path createPath() { if (path == null) { path = new Path(project); } @@ -167,27 +174,24 @@ Format Options 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){ + if (outFile == null) { 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)"); } // 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"); } + tmpFile = createTmpFile(); } 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 @@ -197,8 +201,8 @@ Format Options protected void transformFile() throws BuildException { FileInputStream tmpStream = null; try { - tmpStream = new FileInputStream( tmpFile ); - } catch (IOException e){ + tmpStream = new FileInputStream(tmpFile); + } catch (IOException e) { throw new BuildException("Error reading temporary file: " + tmpFile, e); } FileOutputStream xmlStream = null; @@ -208,29 +212,31 @@ Format Options xmlHandler.setProcessOutputStream(tmpStream); xmlHandler.start(); xmlHandler.stop(); - } catch (IOException e){ + } catch (IOException e) { throw new BuildException("Error creating output file: " + outFile, e); } finally { - if (xmlStream != null){ + if (xmlStream != null) { try { xmlStream.close(); - } catch (IOException ignored){} + } catch (IOException ignored) { + } } - if (tmpStream != null){ + if (tmpStream != null) { try { tmpStream.close(); - } catch (IOException ignored){} + } catch (IOException ignored) { + } } } } - + /** cleanup the temporary txt report */ protected void cleanUp() throws BuildException { try { super.cleanUp(); } finally { - if (tmpFile != null){ + if (tmpFile != null) { tmpFile.delete(); tmpFile = null; } @@ -242,52 +248,52 @@ Format Options * 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(){ + 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(){ + 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){ + 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){ + if (classPath != null) { 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 // options.addElement( "-quiet"); - options.addElement( "-format"); - + 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"); - + options.addElement("tab"); + // specify a / as the indent character, used by the handler. - options.addElement( "-i"); - options.addElement( "/"); - + options.addElement("-i"); + options.addElement("/"); + // directories 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()); return options; } diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java b/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java index da9a8315f..0aeab1b0f 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java @@ -54,32 +54,36 @@ 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.InputStreamReader; import java.io.OutputStream; -import java.io.IOException; 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.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.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 @@ -95,44 +99,44 @@ import org.apache.tools.ant.Project; public class MMetricsStreamHandler implements ExecuteStreamHandler { /** 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' */ - protected final static String PACKAGE = "package"; + private final static String PACKAGE = "package"; /** 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()' */ - 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 */ - protected InputStream metricsOutput; + private InputStream metricsOutput; /** * this is where the XML output will go, should mostly be a file * the caller is responsible for flushing and closing this stream */ - protected OutputStream xmlOutputStream; + private OutputStream xmlOutputStream; /** metrics handler */ - protected TransformerHandler metricsHandler; + private TransformerHandler metricsHandler; /** the task */ - protected Task task; + private Task task; /** * the stack where are stored the metrics element so that they we can * know if we have to close an element or not. */ - protected Stack stack = new Stack(); + private Stack stack = new Stack(); /** initialize this handler */ - MMetricsStreamHandler(Task task, OutputStream xmlOut){ + MMetricsStreamHandler(Task task, OutputStream xmlOut) { this.task = task; this.xmlOutputStream = xmlOut; } @@ -147,34 +151,39 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler { /** Set the inputstream */ public void setProcessOutputStream(InputStream is) throws IOException { - metricsOutput = is; + 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) ){ + 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")) ); + metricsHandler = ((SAXTransformerFactory) factory).newTransformerHandler(); + metricsHandler.setResult(new StreamResult(new OutputStreamWriter(xmlOutputStream, "UTF-8"))); Transformer transformer = metricsHandler.getTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); // start the document with a 'metrics' root + final Date now = new Date(); metricsHandler.startDocument(); AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "company", "company", "CDATA", "metamata"); + attr.addAttribute("", "snapshot_created", "snapshot_created", "CDATA", + DateUtils.format(now, DateUtils.ISO8601_DATETIME_PATTERN)); +// attr.addAttribute("", "elapsed_time", "elapsed_time", "CDATA", String.valueOf(now.getTime() - program_start.getTime())); + attr.addAttribute("", "program_start", "program_start", "CDATA", + DateUtils.format(new Date(), DateUtils.ISO8601_DATETIME_PATTERN)); metricsHandler.startElement("", "metrics", "metrics", attr); // now parse the whole thing parseOutput(); - } catch (Exception e){ - e.printStackTrace(); - throw new IOException(e.getMessage()); + } catch (Exception e) { + throw new BuildException(e); } } @@ -185,15 +194,15 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler { try { // we need to pop everything and close elements that have not been // 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()); } // close the root metricsHandler.endElement("", "metrics", "metrics"); // document is finished for good metricsHandler.endDocument(); - } catch (SAXException e){ + } catch (SAXException e) { e.printStackTrace(); throw new IllegalStateException(e.getMessage()); } @@ -203,7 +212,7 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler { protected void parseOutput() throws IOException, SAXException { BufferedReader br = new BufferedReader(new InputStreamReader(metricsOutput)); String line = null; - while ( (line = br.readLine()) != null ){ + while ((line = br.readLine()) != null) { 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. */ 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; } try { MetricsElement elem = MetricsElement.parse(line); startElement(elem); } catch (ParseException e) { - e.printStackTrace(); + //e.printStackTrace(); // 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 // more elements previous to this one until we got its parent 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. try { - while ( indent <= previous.getIndent() && stack.size() > 0){ + while (indent <= previous.getIndent() && stack.size() > 0) { stack.pop(); 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 @@ -256,7 +266,7 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler { metricsHandler.startElement("", type, type, attrs); // 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 * METHOD. */ - protected String getConstructType(MetricsElement elem){ + protected String getConstructType(MetricsElement elem) { // ok no doubt, it's a file - if ( elem.isCompilationUnit() ){ + if (elem.isCompilationUnit()) { return FILE; } // same, we're sure it's a method - if ( elem.isMethod() ){ + if (elem.isMethod()) { return METHOD; } // if it's empty, and none of the above it should be a package - if ( stack.size() == 0 ){ + 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 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 ){ + 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 ){ + if (prevType.equals(CLASS) && indent >= prevIndent) { return CLASS; } @@ -308,17 +318,16 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler { /** * Create all attributes of a MetricsElement skipping those who have an * empty string - * @param elem */ - protected Attributes createAttributes(MetricsElement 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 ){ + for (; metrics.hasMoreElements(); i++) { + String value = (String) metrics.nextElement(); + if (value.length() > 0) { name = ATTRIBUTES[i]; impl.addAttribute("", name, name, "CDATA", value); } @@ -333,13 +342,16 @@ public class MMetricsStreamHandler implements ExecuteStreamHandler { private final static class ElementEntry { private String type; private int indent; - ElementEntry(String type, int indent){ + + ElementEntry(String type, int indent) { this.type = type; this.indent = indent; } - public String getType(){ + + public String getType() { return type; } + public int getIndent() { return indent; } @@ -351,6 +363,7 @@ class MetricsElement { private final static NumberFormat METAMATA_NF; private final static NumberFormat NEUTRAL_NF; + static { METAMATA_NF = NumberFormat.getInstance(); METAMATA_NF.setMaximumFractionDigits(1); @@ -367,30 +380,30 @@ class MetricsElement { private Vector metrics; - MetricsElement(int indent, String construct, Vector metrics){ + MetricsElement(int indent, String construct, Vector metrics) { this.indent = indent; this.construct = construct; this.metrics = metrics; } - public int getIndent(){ + public int getIndent() { return indent; } - public String getName(){ + public String getName() { return construct; } - public Enumeration getMetrics(){ + public Enumeration getMetrics() { 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 { @@ -399,32 +412,32 @@ class MetricsElement { // 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 ){ + 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 ); + metrics.addElement(token); 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 ! - 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 // construct name, we'll need all this to figure out what type of // construct it is since we lost all semantics :( // (#indent[/]*)(#construct.*) - String name = (String)metrics.elementAt(0); + String name = (String) metrics.elementAt(0); metrics.removeElementAt(0); int indent = 0; pos = name.lastIndexOf('/'); - if (pos != -1){ + if (pos != -1) { name = name.substring(pos + 1); indent = pos + 1; // indentation is last position of token + 1 }