/* * $Id$ * * Copyright 2006, The jCoderZ.org Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the jCoderZ.org Project nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS 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 REGENTS AND 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. */ package org.jcoderz.phoenix.report; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import javax.xml.bind.JAXBException; import javax.xml.transform.TransformerException; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Execute; import org.apache.tools.ant.taskdefs.LogStreamHandler; import org.apache.tools.ant.taskdefs.PumpStreamHandler; import org.apache.tools.ant.types.CommandlineJava; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Environment.Variable; import org.jcoderz.commons.taskdefs.AntTaskUtil; import org.jcoderz.commons.types.Date; import org.jcoderz.commons.util.ArraysUtil; import org.jcoderz.commons.util.FileUtils; import org.jcoderz.commons.util.StringUtil; /** * This is the Ant task for the Jcoderz Report. * This task forks all processing steps as separate processes * so that memory for each process can be controlled separately. * * TODO: Why are the inner classes static + take a JcReportAntTask? * * @author Michael Rumpf */ public class JcReportAntTask extends Task { private static final int DEFAULT_MAX_HEAP = 256; private static final Date CREATION_TIMESTAMP = Date.now(); private static final int DEFAULT_CPUS = 2; private NestedReportsElement mReports = null; private NestedMappingsElement mMappings = null; private NestedToolsElement mTools = null; private final NestedFiltersElement mFilterElements = new NestedFiltersElement(); private NestedLogfilesElement mLogfilesElements = new NestedLogfilesElement(); private String mName = null; private File mDest = null; private File mOldReportFile = null; private String mWikiBase = null; private String mWebRcsBase = null; private String mWebRcsSuffix = null; private String mPackageBase = null; private String mProjectBase = null; private String mStylesheet = null; private File mTempfolder = null; private int mMaxHeap = DEFAULT_MAX_HEAP; private int mCpus = DEFAULT_CPUS; private Charset mSourceEncoding = null; private boolean mDebug = false; private File mWorkingDir = null; /** The global Java Commandline instance */ private final CommandlineJava mCommandline = new CommandlineJava(); private int mMaxInner; /** * @return the number of cpus to put load on. */ public int getCpus () { return mCpus; } /** * @param cpus the cpus to set */ public void setCpus (int cpus) { mCpus = cpus; } /** * @return the sourceEncoding */ public String getEncoding () { return mSourceEncoding.name(); } /** * @param encoding the sourceEncoding to set */ public void setEncoding (String encoding) { mSourceEncoding = Charset.forName(encoding); } /** * Returns the working directory. * * @return the working directory. */ public File getWorkingDir () { return mWorkingDir; } /** * Sets the maximum heap value. * If not defined in the Ant task the default value of 512MB will be used. * * @param maxheap the max heap value. */ public void setMaxHeap (String maxheap) { mMaxHeap = Integer.parseInt(maxheap); } /** * Sets the name of the report. * * @param name the report name. */ public void setName (String name) { mName = name; } /** * Sets the destination of the report. * * @param dest the report destination. */ public void setDest (String dest) { mDest = new File(dest); AntTaskUtil.ensureDirectory(mDest); } public void setPackageBase (String packageBase) { mPackageBase = packageBase; } public void setProjectBase (String projectBase) { mProjectBase = projectBase; } public void setWebRcsBase (String webRcsBase) { mWebRcsBase = webRcsBase; } public void setWebRcsSuffix (String webRcsSuffix) { mWebRcsSuffix = webRcsSuffix; } public void setWikiBase (String wikiBase) { mWikiBase = wikiBase; } public void setOldReportFile (String oldReportFile) { mOldReportFile = new File(oldReportFile); } /** * Sets the stylesheet to be used for the report. * * @param stylesheet the report stylesheet. */ public void setStylesheet (String stylesheet) { mStylesheet = stylesheet; } /** * Sets the temporary folder. * * @param tempfolder the temporary folder. */ public void setTempfolder (String tempfolder) { mTempfolder = new File(tempfolder); } /** * Sets the debug parameter. * * @param debug the debug parameter. */ public void setDebug (Boolean debug) { mDebug = debug.booleanValue(); } public Path createClasspath () { return mCommandline.createClasspath(getProject()).createPath(); } /** * This method is called by Ant for executing this task. * * @throws BuildException whenever a problem occurs. */ public void execute () throws BuildException { try { // Always show this line super.log("Executing JcReportAntTask..."); checkParameters(); // Delete the dest folder in case it exists so that we don't mix // already deleted files. And create a fresh folder afterwards again. if (mDest.exists()) { FileUtils.rmdir(mDest); AntTaskUtil.ensureDirectory(mDest); } // Now start processing the different reports log("Processing reports..."); final int max = Math.min(mCpus + 1 , mReports.getReports().size()); mMaxInner = 1 + (mCpus / max); super.log("Decided to have " + max + " report types with " + mMaxInner + " reports each in parallel."); final CompletionService<File> service = new ExecutorCompletionService<File>( new ThreadPoolExecutor(max, max, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>( mReports.getReports().size()))); final List<Future<File>> jcReports = new ArrayList<Future<File>>(); final Iterator<NestedReportElement> iterReport = mReports.getReports().iterator(); while (iterReport.hasNext()) { final NestedReportElement nre = iterReport.next(); log("Processing report '" + nre.getName() + "' ..."); final Future<File> jcReport = service.submit( new Callable<File> () { public File call () throws InterruptedException, ExecutionException, IOException, JAXBException, TransformerException { final File result; log("Starting: " + nre.getName() + " for " + nre.getSourcePath() + "."); result = performNestedReport(nre); log("Done: " + nre.getName() + " got " + nre.getSourcePath() + "."); return result; } } ); jcReports.add(jcReport); } final File jcReport = executeReportMerger(jcReports); executeJava2Html(jcReport); } catch (Exception ex) { log(ex.toString(), ex, Project.MSG_ERR); // CHECKME! throw new BuildException("An unexpected exception occured!", ex); } } private File performNestedReport (final NestedReportElement nre) throws InterruptedException, ExecutionException, IOException, JAXBException, TransformerException { // Create a temp folder for this report final File reportTmpDir = new File(mWorkingDir, nre.getName()); AntTaskUtil.ensureDirectory(reportTmpDir); final File srcDir = new File(nre.getSourcePath()); final File clsDir = new File(nre.getClassPath()); final CompletionService<File> service = new ExecutorCompletionService<File>( new ThreadPoolExecutor(mMaxInner, mMaxInner, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5))); // 2 max threads? File pmdXml = null; final Future<File> pmdResult = submit(mTools.getPmd(), reportTmpDir, srcDir, clsDir, service); File checkstyleXml = null; final Future<File> checkstyleResult = submit(mTools.getCheckstyle(), reportTmpDir, srcDir, clsDir, service); File findbugsXml = null; final Future<File> findbugsResult = submit(mTools.getFindbugs(), reportTmpDir, srcDir, clsDir, service); File cpdXml = null; final Future<File> cpdResult = submit(mTools.getCpd(), reportTmpDir, srcDir, clsDir, service); File coberturaXml = null; final Future<File> coberturaResult = submit(mTools.getCobertura(), reportTmpDir, srcDir, clsDir, service); // now get the results.... if (checkstyleResult != null) { // EXCEPTION? checkstyleXml = checkstyleResult.get(); } if (findbugsResult != null) { // EXCEPTION? findbugsXml = findbugsResult.get(); } if (pmdResult != null) { // EXCEPTION? pmdXml = pmdResult.get(); } if (cpdResult != null) { // EXCEPTION? cpdXml = cpdResult.get(); } if (coberturaResult != null) { // EXCEPTION? coberturaXml = coberturaResult.get(); } final File emmaFile; if (mTools.getEmma() != null) { // EXCEPTION? emmaFile = new File(mTools.getEmma().mDatafile); } else { emmaFile = null; } // Merge the different reports into one jcoderz-report.xml // This must be done on a level by level basis return executeReportNormalizer(srcDir, reportTmpDir, nre.getLevel(), checkstyleXml, findbugsXml, pmdXml, cpdXml, coberturaXml, emmaFile); } private Future<File> submit (final NestedToolElement nce, final File reportTmpDir, final File srcDir, final File clsDir, CompletionService<File> service) { Future<File> result = null; if (nce != null) { result = service.submit( new Callable<File> () { public File call () { final File result; log("Starting: " + nce.toString() + " for " + srcDir + "."); result = nce.execute(reportTmpDir, srcDir, clsDir); log("Done: " + nce.toString() + " got " + result + "."); return result; } } ); } return result; } private void checkParameters () { if (mTempfolder == null) { throw new BuildException("You must specify a temporary folder!", getLocation()); } AntTaskUtil.ensureDirectory(mTempfolder); mWorkingDir = new File(mTempfolder, mName); AntTaskUtil.ensureDirectory(mWorkingDir); // Check that the names of the reports differ! final Set<String> reportNames = new HashSet<String>(); final Iterator<NestedReportElement> iterReport = mReports.getReports().iterator(); while (iterReport.hasNext()) { final NestedReportElement nre = iterReport.next(); reportNames.add(nre.getName()); } if (reportNames.size() != mReports.getReports().size()) { throw new BuildException("Reports must not have the same names!", getLocation()); } } /** * Executes the report normalizer in a separate process. * * The following command line parameters are supported: * <ul> * <li><code>-cobertura coberturareport.xml</code> * (http://cobertura.sf.net)</li> * <li><code>-checkstyle checkstylereport.xml</code> * (http://checkstyle.sf.net)</li> * <li><code>-findbugs findbugsreport.xml</code> * (http://findbugs.sf.net)</li> * <li><code>-pmd pmdreport.xml</code> * (http://pmd.sf.net)</li> * <li><code>-cpd cpdreport.xml</code> * (http://pmd.sf.net)</li> * <li><code>-projectHome</code></li> * <li><code>-srcDir</code></li> * <li><code>-projectName</code></li> * <li><code>-level PROD|TEST|MISC</code> The weight level</li> * <li><code>-out</code></li> * </ul> */ private File executeReportNormalizer (File srcDir, File reportDir, ReportLevel level, File checkstyleXml, File findbugsXml, File pmdXml, File cpdXml, File coberturaXml, File emmaSummary) throws IOException, JAXBException, TransformerException { // INLINE failed, got java.lang.OutOfMemoryError: PermGen space log("Creating report normalizer command line..."); final CommandlineJava cmd = createCommandlineJava(mCommandline, mMaxHeap); cmd.setClassname("org.jcoderz.phoenix.report.ReportNormalizer"); cmd.createArgument().setValue("-srcDir"); cmd.createArgument().setFile(srcDir); cmd.createArgument().setValue("-level"); cmd.createArgument().setValue(level.toString()); if (mDebug) { cmd.createArgument().setValue("-loglevel"); cmd.createArgument().setValue("FINEST"); } cmd.createArgument().setValue("-projectName"); cmd.createArgument().setValue(mName); cmd.createArgument().setValue("-out"); cmd.createArgument().setFile(reportDir); if (checkstyleXml != null) { cmd.createArgument().setValue("-checkstyle"); cmd.createArgument().setFile(checkstyleXml); } if (findbugsXml != null) { cmd.createArgument().setValue("-findbugs"); cmd.createArgument().setFile(findbugsXml); } if (pmdXml != null) { cmd.createArgument().setValue("-pmd"); cmd.createArgument().setFile(pmdXml); } if (cpdXml != null) { cmd.createArgument().setValue("-cpd"); cmd.createArgument().setFile(cpdXml); } if (coberturaXml != null) { cmd.createArgument().setValue("-cobertura"); cmd.createArgument().setFile(coberturaXml); } if (emmaSummary != null) { cmd.createArgument().setValue("-emma"); cmd.createArgument().setFile(emmaSummary); } for (NestedLogfileElement nge : mLogfilesElements.mGenericReaders) { cmd.createArgument().setValue("-generic"); cmd.createArgument().setFile(nge.getFile()); cmd.createArgument().setValue(nge.getType()); } forkToolProcess(this, cmd, new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN)); return new File(reportDir, ReportNormalizer.JCODERZ_REPORT_XML); } private File executeReportMerger (List<Future<File>> jcReports) throws InterruptedException, ExecutionException, IOException, JAXBException, TransformerException { log("Preparing report merger..."); final ReportMerger merger = new ReportMerger(); if (mDebug) { merger.setLogLevel(Level.ALL); } merger.setOutFile(mWorkingDir); final Iterator<Future<File>> jcReportIter = jcReports.iterator(); while (jcReportIter.hasNext()) { final File jcReport = jcReportIter.next().get(); merger.addReport(jcReport); } final Iterator<NestedFilterElement> filterIter = mFilterElements.getFilters().iterator(); while (filterIter.hasNext()) { final NestedFilterElement filterElement = filterIter.next(); merger.addFilter(filterElement.getFile()); } merger.merge(); merger.filter(); if (mOldReportFile != null) { merger.setOldFile(mOldReportFile); merger.flagNewFindings(); } final File outFile = new File(mWorkingDir, ReportNormalizer.JCODERZ_REPORT_XML); try { FileUtils.copy(outFile, mDest); } catch (IOException e) { throw new BuildException("Could not copy '" + outFile + "' to destination folder '" + mDest + "'!", e, getLocation()); } return outFile; } /** * Executes the Java2Html tool in a separate process. * * The following command line parameters are supported: * <pre> * -outDir * -report * -projectHome * -projectName * -cvsBase * -timestamp * -wikiBase * -reportStyle * -packageBase * </pre> */ private void executeJava2Html (File jcReport) { log("Creating java2html command line..."); final CommandlineJava cmd = createCommandlineJava(mCommandline, mMaxHeap); cmd.setClassname("org.jcoderz.phoenix.report.Java2Html"); // let it run in headless mode to avoid exceptions because of a missing X cmd.createVmArgument().setValue("-Djava.awt.headless=true"); cmd.createArgument().setValue("-outDir"); cmd.createArgument().setFile(mDest); cmd.createArgument().setValue("-report"); cmd.createArgument().setFile(jcReport); cmd.createArgument().setValue("-timestamp"); cmd.createArgument().setValue(CREATION_TIMESTAMP.toString( "yyyyMMddHHmmss")); if (mProjectBase != null) { cmd.createArgument().setValue("-projectHome"); cmd.createArgument().setValue(mProjectBase); } if (mStylesheet != null) { cmd.createArgument().setValue("-reportStyle"); cmd.createArgument().setValue(mStylesheet); } cmd.createArgument().setValue("-projectName"); cmd.createArgument().setValue(mName); cmd.createArgument().setValue("-cvsBase"); cmd.createArgument().setValue(mWebRcsBase); if (!StringUtil.isNullOrBlank(mWebRcsSuffix)) { cmd.createArgument().setValue("-cvsSuffix"); cmd.createArgument().setValue(mWebRcsSuffix); } cmd.createArgument().setValue("-wikiBase"); cmd.createArgument().setValue(mWikiBase); if (mPackageBase != null) { cmd.createArgument().setValue("-packageBase"); cmd.createArgument().setValue(mPackageBase); } if (mSourceEncoding != null) { cmd.createArgument().setValue("-sourceEncoding"); cmd.createArgument().setValue(getEncoding()); } if (mDebug) { cmd.createArgument().setValue("-loglevel"); cmd.createArgument().setValue("FINEST"); } forkToolProcess(this, cmd, new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN)); } // // Reports section // /** * This method is called by Ant to create an instance of the * NestedReportsElement class when the 'reports' tag is read. * * @return the new instance of type NestedReportsElement. */ public NestedReportsElement createReports () { mReports = new NestedReportsElement(this); return mReports; } public static class NestedReportsElement { private List<NestedReportElement> mReports = new ArrayList<NestedReportElement>(); private JcReportAntTask mTask; public NestedReportsElement (JcReportAntTask task) { mTask = task; } public NestedReportElement createReport () { mTask.log("Creating report element..."); final NestedReportElement nre = new NestedReportElement(); mReports.add(nre); return nre; } public List<NestedReportElement> getReports () { return Collections.unmodifiableList(mReports); } } /** * This class represents a <report> tag in an Ant * <code>build.xml</code> file. * * @author Michael Rumpf */ public static class NestedReportElement { private String mName; private ReportLevel mLevel; private String mSourcePath; private String mClassPath; public String getName () { return mName; } public void setName (String name) { mName = name; } public ReportLevel getLevel () { return mLevel; } public void setLevel (String level) { mLevel = ReportLevel.fromString(level); } public String getClassPath () { return mClassPath; } public void setClassPath (String classPath) { mClassPath = classPath; } public String getSourcePath () { return mSourcePath; } public void setSourcePath (String sourcePath) { mSourcePath = sourcePath; } } // // Mappings section // /** * This method is called by Ant to create an instance of the * NestedMappingsElement class when the 'mappings' tag is read. * * @return the new instance of type NestedMappingsElement. */ public NestedMappingsElement createMappings () { mMappings = new NestedMappingsElement(this); return mMappings; } public static class NestedMappingsElement { private List<NestedMappingElement> mMappings = new ArrayList<NestedMappingElement>(); private JcReportAntTask mTask; public NestedMappingsElement (JcReportAntTask task) { mTask = task; } public NestedMappingElement createWebRcs () { mTask.log("Creating mapping element..."); final NestedMappingElement nme = new NestedMappingElement(); mMappings.add(nme); return nme; } public List<NestedMappingElement> getMappings () { return mMappings; } } public static class NestedMappingElement { private String mPattern; private String mUrl; private String mSuffix; public String getPattern () { return mPattern; } public void setPattern (String pattern) { mPattern = pattern; } public String getSuffix () { return mSuffix; } public void setSuffix (String suffix) { mSuffix = suffix; } public String getUrl () { return mUrl; } public void setUrl (String url) { mUrl = url; } } // // Tools section // /** * This method is called by Ant to create an instance of the * NestedToolsElement class when the 'tools' tag is read. * * @return the new instance of type NestedToolsElement. */ public NestedToolsElement createTools () { mTools = new NestedToolsElement(this); return mTools; } public static class NestedToolsElement { private JcReportAntTask mTask; private NestedPmdElement mPmd = null; private NestedCpdElement mCpd = null; private NestedFindbugsElement mFindbugs = null; private NestedCheckstyleElement mCheckstyle = null; private NestedCoberturaElement mCobertura = null; private NestedEmmaElement mEmma = null; public NestedToolsElement (JcReportAntTask task) { mTask = task; } public NestedPmdElement createPmd () { mTask.log("Creating Pmd element..."); mPmd = new NestedPmdElement(mTask); return mPmd; } public NestedPmdElement getPmd () { return mPmd; } public NestedCpdElement createCpd () { mTask.log("Creating Cpd element..."); mCpd = new NestedCpdElement(mTask); return mCpd; } public NestedCpdElement getCpd () { return mCpd; } public NestedFindbugsElement createFindbugs () { mTask.log("Creating Findbugs element..."); mFindbugs = new NestedFindbugsElement(mTask); return mFindbugs; } public NestedFindbugsElement getFindbugs () { return mFindbugs; } public NestedCheckstyleElement createCheckstyle () { mTask.log("Creating Checkstyle element..."); mCheckstyle = new NestedCheckstyleElement(mTask); return mCheckstyle; } public NestedCheckstyleElement getCheckstyle () { return mCheckstyle; } public NestedCoberturaElement createCobertura () { mTask.log("Creating Cobertura element..."); mCobertura = new NestedCoberturaElement(mTask); return mCobertura; } public NestedCoberturaElement getCobertura () { return mCobertura; } public NestedEmmaElement createEmma () { mTask.log("Creating Emma element..."); mEmma = new NestedEmmaElement(mTask); return mEmma; } public NestedEmmaElement getEmma () { return mEmma; } } /** * This is the base class for all tool elements. * It provides support for the maxheap attribute * and the nested classpath element. * * @author Michael Rumpf */ public abstract static class NestedToolElement { protected JcReportAntTask mTask; protected Path mPath; protected int mMaxHeap; /** The global Java Commandline instance */ protected final CommandlineJava mCommandline = new CommandlineJava(); public NestedToolElement (JcReportAntTask task) { mTask = task; mMaxHeap = mTask.mMaxHeap; } /** * Sets the maximum heap value. * If not defined in the Ant task the default value of 512MB will be used. * * @param maxheap the max heap value. */ public void setMaxheap (String maxheap) { mMaxHeap = Integer.parseInt(maxheap); } /** * Creates a classpath for the tool element. * * @return the created classpath. */ public Path createClasspath () { mPath = mCommandline.createClasspath(mTask.getProject()).createPath(); return mPath; } public abstract File execute (File reportDir, File srcDir, File clsDir); } public static class NestedPmdElement extends NestedToolElement { private String mConfig; private String mTargetjdk; private String mEncoding; public NestedPmdElement (JcReportAntTask task) { super(task); mCommandline.setClassname("net.sourceforge.pmd.PMD"); } public void setConfig (String config) { mConfig = config; } public void setTargetjdk (String targetjdk) { mTargetjdk = targetjdk; } public void setEncoding (String encoding) { mEncoding = encoding; } public File execute (File reportDir, File srcDir, File clsDir) { mTask.log("Creating pmd command line..."); final CommandlineJava cmd = createCommandlineJava(mCommandline, mMaxHeap); cmd.createArgument().setFile(srcDir); // We always write pmd reports in XML format cmd.createArgument().setValue("xml"); if (mConfig != null) { cmd.createArgument().setFile(new File(mConfig)); } if (mEncoding != null) { cmd.createArgument().setValue("-encoding"); cmd.createArgument().setValue(mEncoding); } else if (mTask.getEncoding() != null) { cmd.createArgument().setValue("-encoding"); cmd.createArgument().setValue(mTask.getEncoding()); } if (mTargetjdk != null) { cmd.createArgument().setValue("-targetjdk"); cmd.createArgument().setValue(mTargetjdk); } final File outFile = new File(reportDir, "pmd.xml"); FileOutputStream fos = null; try { fos = new FileOutputStream(outFile); } catch (IOException e) { throw new BuildException("Could not find output file: " + outFile.getAbsolutePath(), e, mTask.getLocation()); } forkToolProcess(mTask, cmd, new PumpStreamHandler(fos, System.err)); return outFile; } } public static class NestedCpdElement extends NestedToolElement { private static final int DEFAULT_MINIMUM_TOKENS = 100; private int mMinimumtokens = DEFAULT_MINIMUM_TOKENS; private String mEncoding = null; private String mOutputEncoding = "UTF-8"; public NestedCpdElement (JcReportAntTask task) { super(task); mCommandline.setClassname("net.sourceforge.pmd.cpd.CPD"); } public void setMinimumtokens (String minimumtokens) { mMinimumtokens = Integer.parseInt(minimumtokens); } public void setEncoding (String encoding) { mEncoding = encoding; } public void setOutputEncoding (String encoding) { mOutputEncoding = encoding; } /** * Executes the cpd tool in a separate process. * * The following command line switches are supported by this method: * <pre> * CPD --minimum-tokens xxx --files xxx * </pre> */ public File execute (File reportDir, File srcDir, File clsDir) { mTask.log("Creating cpd command line..."); final CommandlineJava cmd = createCommandlineJava(mCommandline, mMaxHeap); final Variable var = new Variable(); var.setKey("file.encoding"); var.setValue(mOutputEncoding); cmd.getSystemProperties().addVariable(var); cmd.createArgument().setFile(srcDir); // We always write pmd reports in XML format cmd.createArgument().setValue("--format"); cmd.createArgument().setValue("net.sourceforge.pmd.cpd.XMLRenderer"); if (mEncoding != null) { cmd.createArgument().setValue("--encoding"); cmd.createArgument().setValue(mEncoding); } else if (mTask.getEncoding() != null) { cmd.createArgument().setValue("--encoding"); cmd.createArgument().setValue(mTask.getEncoding()); } cmd.createArgument().setValue("--language"); cmd.createArgument().setValue("java"); cmd.createArgument().setValue("--files"); cmd.createArgument().setFile(srcDir); cmd.createArgument().setValue("--minimum-tokens"); cmd.createArgument().setValue(String.valueOf(mMinimumtokens)); final File outFile = new File(reportDir, "cpd.xml"); FileOutputStream fos = null; try { fos = new FileOutputStream(outFile); } catch (IOException e) { throw new BuildException("Could not find output file: " + outFile.getAbsolutePath(), e, mTask.getLocation()); } forkToolProcess(mTask, cmd, new PumpStreamHandler(fos, System.err)); return outFile; } } public static class NestedFindbugsElement extends NestedToolElement { private String mConfig; private String mWarninglevel = "medium"; private String mEffort = "default"; private String mOmitVisitors = ""; private Path mAuxPath; private boolean mFindBugsDebug = false; /** * Path of the findbugs plugin jar files. Must at least contain * the coreplugin.jar */ private Path mPluginList; public NestedFindbugsElement (JcReportAntTask task) { super(task); mCommandline.setClassname("edu.umd.cs.findbugs.FindBugs2"); } /** * Sets the debug parameter. * @param debug the debug parameter. */ public void setDebug (Boolean debug) { mFindBugsDebug = debug.booleanValue(); } public void setOmitVisitors (String omitVisitors) { mOmitVisitors = omitVisitors; } public void setConfig (String config) { mConfig = config; } public void setEffort (String effort) { if ("min".equals(effort) || "default".equals(effort) || "max".equals(effort)) { mEffort = effort; } else { mTask.log("Invalid effort value '" + effort + "!'"); } } public void setWarninglevel (String warninglevel) { if ("experimental".equals(warninglevel) || "low".equals(warninglevel) || "medium".equals(warninglevel) || "high".equals(warninglevel)) { mWarninglevel = warninglevel; } else { mTask.log("Invalid warninglevel value '" + warninglevel + "!'"); } } /** * The findbugs tool needs an list of jar files where all the plugins are * defined in. Minimum plugin list contains the coreplugin. * * @return the created plugin list path. */ public Path createPluginlist () { mPluginList = new Path(mTask.getProject()); return mPluginList; } /** * The findbugs tool needs an auxiliary classpath with all the classes, * referenced from the project class files. * * @return the created auxiliary classpath. */ public Path createAuxclasspath () { mAuxPath = new Path(mTask.getProject()); return mAuxPath; } /** * Executes the findbugs tool in a separate process. * <pre> * maxheap: * -maxHeap size Maximum Java heap size in megabytes (default=256) * * effort: * -effort[:min|default|max] set analysis effort level * * warninglevel: * -experimental report all warnings including experimental bug * patterns * -low report all warnings * -medium report only medium and high priority warnings * [default] * -high report only high priority warnings * * config: * -exclude <filter file> include only bugs matching given filter * * internally: * -outputFile <filename> Save output in named file * -xml[:withMessages] XML output (optionally with messages) * * auxclasspath: * -auxclasspath <classpath> set aux classpath for analysis * * report: sourcepath * -sourcepath <source path> set source path for analyzed classes * </pre> * The target assumes that all libs needed by findbugs are on the * classpath and the plugins are set via pluginlist element. * */ public File execute (File reportDir, File srcDir, File clsDir) { mTask.log("Creating findbugs command line..."); final CommandlineJava cmd = createCommandlineJava(mCommandline, mMaxHeap); if (mFindBugsDebug) { cmd.createVmArgument().setValue("-Dfindbugs.debug=true"); } if (mPluginList != null) { cmd.createArgument().setValue("-pluginList"); cmd.createArgument().setPath(mPluginList); } if (!StringUtil.isEmptyOrNull(mOmitVisitors)) { cmd.createArgument().setValue("-omitVisitors"); cmd.createArgument().setValue(mOmitVisitors); } final File outFile = new File(reportDir, "findbugs.xml"); cmd.createArgument().setValue("-output"); cmd.createArgument().setFile(outFile); cmd.createArgument().setValue("-sourcepath"); cmd.createArgument().setFile(srcDir); // We always write findbugs reports in XML format cmd.createArgument().setValue("-xml:withMessages"); if (mConfig != null) { cmd.createArgument().setValue("-exclude"); cmd.createArgument().setFile(new File(mConfig)); } if (mAuxPath != null) { cmd.createArgument().setValue("-auxclasspath"); cmd.createArgument().setPath(mAuxPath); } cmd.createArgument().setValue("-" + mWarninglevel); cmd.createArgument().setValue("-effort:" + mEffort); cmd.createArgument().setFile(clsDir); // TODO: use PumpStreamHandler to suppress info messages from FindBugs forkToolProcess(mTask, cmd, new LogStreamHandler(mTask, Project.MSG_INFO, Project.MSG_WARN)); return outFile; } } public static class NestedCheckstyleElement extends NestedToolElement { private String mConfig; private String mProperties; public NestedCheckstyleElement (JcReportAntTask task) { super(task); mCommandline.setClassname("com.puppycrawl.tools.checkstyle.Main"); } public void setConfig (String config) { mConfig = config; } public void setProperties (String properties) { mProperties = properties; } public String toString () { return "Checkstyle"; } public File execute (File reportDir, File srcDir, File clsPath) { mTask.log("Creating checkstyle command line..."); final CommandlineJava cmd = createCommandlineJava(mCommandline, mMaxHeap); cmd.createArgument().setValue("-o"); final File outFile = new File(reportDir, "checkstyle.xml"); cmd.createArgument().setFile(outFile); if (mConfig == null) { throw new BuildException("The 'config' attribute is mandatory" + " for the checkstyle task!", mTask.getLocation()); } cmd.createArgument().setValue("-c"); cmd.createArgument().setFile(new File(mConfig)); // We always write checkstyle reports in XML format cmd.createArgument().setValue("-f"); cmd.createArgument().setValue("xml"); if (mProperties != null) { cmd.createArgument().setValue("-p"); cmd.createArgument().setFile(new File(mProperties)); } cmd.createArgument().setValue("-r"); cmd.createArgument().setFile(srcDir); if (mTask.getEncoding() != null) { final Variable var = new Variable(); var.setKey("file.encoding"); var.setValue(mTask.getEncoding()); cmd.getSystemProperties().addVariable(var); } forkToolProcess(mTask, cmd, new LogStreamHandler(mTask, Project.MSG_INFO, Project.MSG_WARN)); return outFile; } } public static class NestedCoberturaElement extends NestedToolElement { private String mDatafile; public NestedCoberturaElement (JcReportAntTask task) { super(task); mCommandline.setClassname("net.sourceforge.cobertura.reporting.Main"); } public void setDatafile (String datafile) { mDatafile = datafile; } /** * Executes the cobertura tool in a separate process. * * <pre> * [--datafile file] * [--destination dir] * source code directory [...] * </pre> */ public File execute (File reportDir, File srcDir, File clsPath) { mTask.log("Creating cobertura command line..."); final CommandlineJava cmd = createCommandlineJava(mCommandline, mMaxHeap); File dataFile = null; if (mDatafile == null) { throw new BuildException("The datafile attribute is mandatory!", mTask.getLocation()); } dataFile = new File(mDatafile); if (!dataFile.exists()) { throw new BuildException( "The datafile '" + mDatafile + "' was not found!", mTask.getLocation()); } cmd.createArgument().setValue("--destination"); final File outFile = new File(reportDir, "coverage.xml"); cmd.createArgument().setFile(reportDir); // We always write checkstyle reports in XML format cmd.createArgument().setValue("--format"); cmd.createArgument().setValue("xml"); cmd.createArgument().setValue("--datafile"); cmd.createArgument().setFile(dataFile); cmd.createArgument().setFile(srcDir); forkToolProcess(mTask, cmd, new LogStreamHandler(mTask, Project.MSG_INFO, Project.MSG_WARN)); return outFile; } } public static class NestedEmmaElement extends NestedToolElement { private String mDatafile; public NestedEmmaElement (JcReportAntTask task) { super(task); } public void setDatafile (String datafile) { mDatafile = datafile; } /** * Nothing to be done for emma. */ public File execute (File reportDir, File srcDir, File clsPath) { return new File(mDatafile); } } // // Generic input // /** * This method is called by Ant to create an instance of the * NestedLogfilesElement class when the 'logfiles' tag is read. * * @return the new instance of type NestedFiltersElement. */ public NestedLogfilesElement createLogfiles () { return mLogfilesElements; } public static class NestedLogfilesElement { private List<NestedLogfileElement> mGenericReaders = new ArrayList<NestedLogfileElement>(); public void addLogfile (NestedLogfileElement nge) { mGenericReaders.add(nge); } public List<NestedLogfileElement> getLogfile () { return mGenericReaders; } } public static class NestedLogfileElement { private String mType; private File mFile; public File getFile () { return mFile; } public String getType () { return mType; } public void setFile (File file) { mFile = file; } public void setType (String type) { mType = type; } } // // Filters section // /** * This method is called by Ant to create an instance of the * NestedFiltersElement class when the 'filters' tag is read. * * @return the new instance of type NestedFiltersElement. */ public NestedFiltersElement createFilters () { return mFilterElements; } public static class NestedFiltersElement { private List<NestedFilterElement> mFilters = new ArrayList<NestedFilterElement>(); public void addFilter (NestedFilterElement nfe) { mFilters.add(nfe); } public List<NestedFilterElement> getFilters () { return mFilters; } } public static class NestedFilterElement { private File mFile; public File getFile () { return mFile; } public void setFile (String file) { mFile = new File(file); } } // // Helper methods // /** * Creates a copy of the global command line instance * and sets the maximum heap vm parameter. * * @param cmdline the global command line instance. * @param maxHeap the maximum heap size for the process. * @return a copy of the global command line instance. */ private static CommandlineJava createCommandlineJava ( CommandlineJava cmdline, int maxHeap) { final CommandlineJava cmd; try { cmd = (CommandlineJava) cmdline.clone(); } catch (CloneNotSupportedException unexpected) { throw new RuntimeException( "Ups, CommandLineJava doesn't support the method clone()", unexpected); } cmd.createVmArgument().setValue("-Xmx" + maxHeap + "m"); return cmd; } /** * Forks the tool as external process. * * @param cmdline the command line. * @param psh the pump stream handler for redirecting the process streams. */ private static void forkToolProcess (JcReportAntTask task, CommandlineJava cmdline, PumpStreamHandler psh) { final Execute execute = new Execute(psh); execute.setCommandline(cmdline.getCommandline()); try { task.logCommandLine(cmdline.getCommandline()); final int exitCode = execute.execute(); if (exitCode != 0) { task.log("Process returned with exit code: " + exitCode); } } catch (IOException e) { throw new BuildException( "Process fork failed. " + ArraysUtil.toString(cmdline.getCommandline()), e, task.getLocation()); } } /** * This is a special logging method to print the array of command * line parameters to the ant logging sub-system. * * @param cmdLine the command line parameter array. */ private void logCommandLine (String[] cmdLine) { log("Command line: "); for (int i = 0; i < cmdLine.length; i++) { log(" " + cmdLine[i]); } } /** * Overwrites the method from the super class in order to * check for debug mode. * * @param msg the message to log. */ public void log (String msg) { if (mDebug) { super.log(msg); } } }