/*
* $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.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.jcoderz.commons.util.FileUtils;
import org.jcoderz.commons.util.IoUtil;
import org.jcoderz.commons.util.LoggingUtils;
import org.jcoderz.phoenix.report.jaxb.Item;
import org.jcoderz.phoenix.report.jaxb.ObjectFactory;
/**
* Provides merging of findbugs, pmd, checkstyle, cpd, and cobertura
* XML files into a single XML representation.
*
* @author Michael Griffel
* @author Michael Rumpf
*/
public final class ReportNormalizer
{
/** The Constant JCODERZ_REPORT_XML. */
public static final String JCODERZ_REPORT_XML
= "jcoderz-report.xml";
/** The Constant CLASSNAME. */
private static final String CLASSNAME
= ReportNormalizer.class.getName();
/** The Constant logger. */
private static final Logger logger
= Logger.getLogger(CLASSNAME);
/** The project home. */
private File mProjectHome;
/** The project name. */
private String mProjectName = "Unknown Project";
/** The out file. */
private File mOutFile;
/** The log level. */
private Level mLogLevel = Level.INFO;
/** The report level. */
private ReportLevel mLevel = ReportLevel.PROD;
/** The report list. */
private List<SourceReport> mReportList
= new ArrayList<SourceReport>();
/** The src list. */
private List<SourceReport> mSrcList
= new ArrayList<SourceReport>();
/**
* The XSL stylesheet that can be used to filter the
* jcoderz-report XML file.
*/
private File mFilterFile = null;
/**
* Constructor.
*
* @throws IOException in case of any error.
*/
public ReportNormalizer ()
throws IOException
{
mProjectHome = new File(".").getCanonicalFile();
mOutFile = new File(JCODERZ_REPORT_XML);
}
/**
* Main method.
*
* @param args arguments.
*
* @throws Exception the exception
*/
public static void main (String[] args)
throws Exception
{
try
{
final ReportNormalizer rn = new ReportNormalizer();
rn.parseArguments(args);
rn.run();
}
catch (Exception e)
{
logger.log(Level.WARNING, "Failed in Normalizer.", e);
throw e;
}
}
/**
* Run ReportNormalizer.
*/
public void run () throws JAXBException, IOException, TransformerException
{
logger.fine("Running report normalizer on #" + mReportList.size()
+ " reports ...");
final Map<ResourceInfo, List<Item>> items
= new HashMap<ResourceInfo, List<Item>>();
for (SourceReport report : mSrcList)
{
handleReport(report, items);
}
for (SourceReport report : mReportList)
{
handleReport(report, items);
}
final JcoderzReport myReport = new JcoderzReport();
myReport.setProjectHome(mProjectHome.getAbsolutePath());
myReport.setProjectName(mProjectName);
myReport.setLevel(mLevel);
// XML report
final OutputStream out = new FileOutputStream(mOutFile);
try
{
myReport.write(out, items);
}
finally
{
IoUtil.close(out);
}
// apply filters to the report
if (mFilterFile != null)
{
filter();
}
}
/**
* Handle report.
*
* @param report the report
* @param items the items
*
* @throws JAXBException the JAXB exception
*/
private void handleReport (SourceReport report,
final Map<ResourceInfo, List<Item>> items)
throws JAXBException
{
try
{
logger.fine("Processing report " + report.getReportFormat()
+ " '" + report.getFilename() + "'");
if (report.getFilename().length() != 0
|| report.getFilename().isDirectory())
{
final ReportReader reportReader
= ReportReaderFactory.createReader(report);
reportReader.parse(report.getFilename());
reportReader.merge(items);
}
else
{
logger.fine("Good job, no findings reported by "
+ report.getReportFormat());
}
}
catch (Exception e)
{
logger.log(Level.SEVERE, "Error while processing", e);
final Item item = new ObjectFactory().createItem();
item.setMessage("Error while Processing '"
+ report.getReportFormat() + "' '"
+ report.getFilename() + "' got Exception: '" + e + "'.");
item.setSeverity(Severity.ERROR);
item.setFindingType(SystemFindingType.SYS_PARSE_ERROR.getSymbol());
item.setOrigin(Origin.SYSTEM);
final ResourceInfo res
= ResourceInfo.register(report.getFilename().getAbsolutePath(),
"", report.getFilename().getAbsolutePath());
if (items.containsKey(res))
{
items.get(res).add(item);
}
else
{
final List<Item> list = new ArrayList<Item>();
list.add(item);
items.put(res, list);
}
}
}
/**
* Filters the report XML file using the JDK XSL processor.
*
* @throws TransformerFactoryConfigurationError
* the transformer factory configuration error
* @throws TransformerConfigurationException
* the transformer configuration exception
* @throws IOException Signals that an I/O exception has occurred.
* @throws TransformerException the transformer exception
* @throws FileNotFoundException the file not found exception
*/
private void filter ()
throws TransformerFactoryConfigurationError,
TransformerConfigurationException, IOException,
TransformerException, FileNotFoundException
{
logger.log(Level.FINE, "Filter: " + mFilterFile);
final TransformerFactory tFactory = TransformerFactory.newInstance();
final Transformer transformer = tFactory.newTransformer(
new StreamSource(mFilterFile));
final File tempOutputFile = new File(
mOutFile.getCanonicalPath() + ".tmp");
FileUtils.createNewFile(tempOutputFile);
final FileOutputStream out = new FileOutputStream(tempOutputFile);
try
{
transformer.transform(new StreamSource(mOutFile),
new StreamResult(out));
}
finally
{
IoUtil.close(out);
}
FileUtils.copyFile(tempOutputFile, mOutFile);
FileUtils.delete(tempOutputFile);
}
/**
* The following parameters select the different reports
* to combine into a single report.
*
* <ul>
* <li><code>-jcoverage jvoveragereport.xml</code> (http://???)</li>
* <li><code>-cobertura coberturareport.xml</code> (http://???)</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://))))</li>
* <li><code>-generic javadoc javadoc.log</code> (http://))))</li>
* </ul>
*
* <ul>
* <li><code>-projectHome</code></li>
* <li><code>-filter filter.xsl</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>-loglevel</code></li>
* <li><code>-out</code></li>
* </ul>
*
* @param args The command line arguments
*
* @return The list of reports to normalize
*
* @throws IOException When the filter file cannot be found
*/
private void parseArguments (String[] args)
throws IOException
{
try
{
for (int i = 0; i < args.length; )
{
logger.fine("Parsing argument '" + args[i] + "' = '"
+ args[i + 1] + "'");
if (args[i].equals("-jcoverage"))
{
addReport(ReportFormat.JCOVERAGE, args[i + 1]);
}
else if (args[i].equals("-cobertura"))
{
addReport(ReportFormat.COBERTURA, args[i + 1]);
}
else if (args[i].equals("-checkstyle"))
{
addReport(ReportFormat.CHECKSTYLE, args[i + 1]);
}
else if (args[i].equals("-findbugs"))
{
addReport(ReportFormat.FINDBUGS, args[i + 1]);
}
else if (args[i].equals("-pmd"))
{
addReport(ReportFormat.PMD, args[i + 1]);
}
else if (args[i].equals("-emma"))
{
addReport(ReportFormat.EMMA, args[i + 1]);
}
else if (args[i].equals("-cpd"))
{
addReport(ReportFormat.CPD, args[i + 1]);
}
else if (args[i].equals("-generic"))
{
addReport(ReportFormat.GENERIC, args[++i], args[i + 1]);
}
else if (args[i].equals("-projectHome"))
{
setProjectHome(new File(args[i + 1]));
}
else if (args[i].equals("-filter"))
{
setFilterFile(new File(args[i + 1]));
}
else if (args[i].equals("-srcDir"))
{
addReport(ReportFormat.SOURCE_DIRECTORY, args[i + 1]);
}
else if (args[i].equals("-projectName"))
{
setProjectName(args[i + 1]);
}
else if (args[i].equals("-level"))
{
setLevel(ReportLevel.fromString(args[i + 1]));
}
else if (args[i].equals("-loglevel"))
{
setLogLevel(Level.parse(args[i + 1]));
}
else if (args[i].equals("-out"))
{
setOutFile(new File(args[i + 1]));
}
else
{
throw new IllegalArgumentException(
"Invalid argument '" + args[i] + "'");
}
++i;
++i;
}
}
catch (IndexOutOfBoundsException e)
{
final IllegalArgumentException ex = new IllegalArgumentException(
"Missing value for " + args[args.length - 1]);
ex.initCause(e);
throw ex;
}
}
/**
* Adds the report.
*
* @param format the format
* @param file the file
*/
public void addReport (ReportFormat format, String file)
{
addReport(format, new File(file));
}
/**
* Adds the report with a given flavor.
* The flavor is used for generic reports to detect the type of file.
*
* @param format the format
* @param file the file
* @param flavor the flavor of the report.
*/
public void addReport (ReportFormat format, String file, String flavor)
{
mReportList.add(new SourceReport(format, new File(file), flavor));
}
/**
* Adds the report.
*
* @param format the format
* @param file the file
*/
public void addReport (ReportFormat format, File file)
{
if (format == ReportFormat.SOURCE_DIRECTORY)
{
addSource(file);
}
else
{
mReportList.add(new SourceReport(format, file));
}
}
public void addSource (File srcDir)
{
mSrcList.add(new SourceReport(
ReportFormat.SOURCE_DIRECTORY, srcDir));
}
/**
* Gets the project home.
*
* @return the project home
*/
public File getProjectHome ()
{
return mProjectHome;
}
/**
* Sets the project home.
*
* @param projectHome the new project home
*/
public void setProjectHome (File projectHome)
{
mProjectHome = projectHome;
}
/**
* Gets the project name.
*
* @return the project name
*/
public String getProjectName ()
{
return mProjectName;
}
/**
* Sets the project name.
*
* @param projectName the new project name
*/
public void setProjectName (String projectName)
{
mProjectName = projectName;
}
/**
* Gets the out file.
*
* @return the out file
*/
public File getOutFile ()
{
return mOutFile;
}
/**
* Sets the out file.
*
* @param outFile the new out file
*
* @throws IOException Signals that an I/O exception has occurred.
*/
public void setOutFile (File outFile) throws IOException
{
if (outFile.isDirectory())
{
mOutFile
= new File(outFile, JCODERZ_REPORT_XML).getCanonicalFile();
}
else
{
mOutFile = outFile.getCanonicalFile();
}
}
/**
* Gets the log level.
*
* @return the log level
*/
public Level getLogLevel ()
{
return mLogLevel;
}
/**
* Sets the log level.
*
* @param logLevel the new log level
*/
public void setLogLevel (Level logLevel)
{
mLogLevel = logLevel;
LoggingUtils.setGlobalHandlerLogLevel(mLogLevel);
logger.config("Setting log level: " + mLogLevel);
Logger.getLogger("org.jcoderz.phoenix.report")
.setLevel(mLogLevel);
}
/**
* Gets the level.
*
* @return the level
*/
public ReportLevel getLevel ()
{
return mLevel;
}
/**
* Sets the level.
*
* @param level the new level
*/
public void setLevel (ReportLevel level)
{
mLevel = level;
}
/**
* Gets the filter file.
*
* @return the filter file
*/
public File getFilterFile ()
{
return mFilterFile;
}
/**
* Sets the filter file.
*
* @param filterFile the new filter file
*
* @throws IOException Signals that an I/O exception has occurred.
*/
public void setFilterFile (File filterFile)
throws IOException
{
mFilterFile = filterFile;
// Do not fail here if the argument is invalid.
if (!mFilterFile.exists())
{
throw new IOException("Filter file '" + mFilterFile
+ "' does not exists.");
}
}
/**
* The Class SourceReport.
*/
public static final class SourceReport
{
/** The report format. */
private final ReportFormat mReportFormat;
/** The filename. */
private final File mFilename;
/** The flavor. */
private final String mFlavor;
/**
* Instantiates a new source report.
* No check here. Let the report parsing fail.
* @param r the ReportFormat
* @param f the File
*/
SourceReport (ReportFormat r, File f)
{
mReportFormat = r;
mFilename = f;
mFlavor = null;
}
/**
* Instantiates a new source report.
* No check here. Let the report parsing fail.
* @param r the ReportFormat
* @param f the File
* @param flavor the report flavor
*/
SourceReport (ReportFormat r, File f, String flavor)
{
mReportFormat = r;
mFilename = f;
mFlavor = flavor;
}
/**
* Returns the filename.
*
* @return the filename.
*/
File getFilename ()
{
return mFilename;
}
/**
* Returns the reportFormat.
*
* @return the reportFormat.
*/
ReportFormat getReportFormat ()
{
return mReportFormat;
}
/**
* Returns the report flavor.
*
* @return the report flavor.
*/
public String getFlavor ()
{
return mFlavor;
}
}
}