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); | |||||
| } | |||||
| } | |||||