package org.codehaus.mojo.javancss;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.codehaus.doxia.site.renderer.SiteRenderer;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.PathTool;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
/**
* Generates a JavaNCSS report based on this module's source code.
*
* @goal report
* @author <a href="jeanlaurentATgmail.com">Jean-Laurent de Morlhon</a>
* @version $Id$
*/
public class NcssReportMojo
extends AbstractMavenReport
{
private static final String OUTPUT_NAME = "javancss";
/**
* Specifies the directory where the HTML report will be generated.
*
* @parameter expression="${project.reporting.outputDirectory}"
* @required
* @readonly
*/
private File outputDirectory;
/**
* Specifies the directory where the XML report will be generated.
*
* @parameter default-value="${project.build.directory}"
* @required
*/
private File xmlOutputDirectory;
/**
* Specifies the location of the source files to be used.
*
* @parameter expression="${project.build.sourceDirectory}"
* @required
* @readonly
*/
private File sourceDirectory;
/**
* Specifies the encoding of the source files.
*
* @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}"
*/
private String sourceEncoding;
/**
* Specifies the maximum number of lines to take into account into the reports.
*
* @parameter default-value="30"
* @required
*/
private int lineThreshold;
/**
* Specified the name of the temporary file generated by JavaNCSS prior report generation.
*
* @parameter default-value="javancss-raw-report.xml"
* @required
*/
private String tempFileName;
/**
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* @component role="org.codehaus.doxia.site.renderer.SiteRenderer"
* @required
* @readonly
*/
private SiteRenderer siteRenderer;
/**
* The projects in the reactor for aggregation report.
*
* @parameter expression="${reactorProjects}"
* @readonly
*/
private List reactorProjects;
/**
* Link the violation line numbers to the source xref. Defaults to true and will link automatically if jxr plugin is
* being used.
*
* @parameter expression="${linkXRef}" default-value="true"
*/
private boolean linkXRef;
/**
* Location of the Xrefs to link to.
*
* @parameter default-value="${project.build.directory}/site/xref"
*/
private File xrefLocation;
/**
* List of ant-style patterns used to specify the java sources that should be included when running JavaNCSS. If
* this is not specified, all .java files in the project source directories are included.
*
* @parameter
*/
private String[] includes;
/**
* List of ant-style patterns used to specify the java sources that should be excluded when running JavaNCSS. If
* this is not specified, no files in the project source directories are excluded.
*
* @parameter
*/
private String[] excludes;
/**
* Gets the source files encoding.
*
* @return The source file encoding.
*/
protected String getSourceEncoding()
{
return sourceEncoding;
}
/**
* @see org.apache.maven.reporting.MavenReport#executeReport(java.util.Locale)
*/
public void executeReport( Locale locale )
throws MavenReportException
{
if ( !canGenerateReport() )
{
return;
}
if ( canGenerateSingleReport() )
{
generateSingleReport( locale );
}
if ( canGenerateAggregateReport() )
{
generateAggregateReport( locale );
}
}
private void generateAggregateReport( Locale locale )
throws MavenReportException
{
// All this work just to get "target" so that we can scan the filesystem for
// child javancss xml files...
String basedir = project.getBasedir().toString();
String output = xmlOutputDirectory.toString();
if ( getLog().isDebugEnabled() )
{
getLog().debug( "basedir: " + basedir );
getLog().debug( "output: " + output );
}
String relative = null;
if ( output.startsWith( basedir ) )
{
relative = output.substring( basedir.length() + 1 );
}
else
{
getLog().error(
"Unable to aggregate report because I can't "
+ "determine the relative location of the XML report" );
return;
}
getLog().debug( "relative: " + relative );
List reports = new ArrayList();
for ( Iterator it = reactorProjects.iterator(); it.hasNext(); )
{
MavenProject child = (MavenProject) it.next();
File xmlReport = new File( child.getBasedir() + File.separator + relative, tempFileName );
if ( xmlReport.exists() )
{
reports.add( new ModuleReport( child, loadDocument( xmlReport ) ) );
}
else
{
getLog().debug( "xml file not found: " + xmlReport );
}
}
getLog().debug( "Aggregating " + reports.size() + " JavaNCSS reports" );
// parse the freshly generated file and write the report
NcssAggregateReportGenerator reportGenerator =
new NcssAggregateReportGenerator( getSink(), getBundle( locale ), getLog() );
reportGenerator.doReport( locale, reports, lineThreshold );
}
private boolean isIncludeExcludeUsed()
{
return ( ( excludes != null ) || ( includes != null ) );
}
private void generateSingleReport( Locale locale )
throws MavenReportException
{
if ( getLog().isDebugEnabled() )
{
getLog().debug( "Calling NCSSExecuter with src : " + sourceDirectory );
getLog().debug( "Calling NCSSExecuter with output : " + buildOutputFileName() );
getLog().debug( "Calling NCSSExecuter with includes : " + includes );
getLog().debug( "Calling NCSSExecuter with excludes : " + excludes );
getLog().debug( "Calling NCSSExecuter with encoding : " + getSourceEncoding() );
}
// run javaNCss and produce an temp xml file
NcssExecuter ncssExecuter;
if ( isIncludeExcludeUsed() )
{
ncssExecuter = new NcssExecuter( scanForSources(), buildOutputFileName() );
}
else
{
ncssExecuter = new NcssExecuter( sourceDirectory, buildOutputFileName() );
}
ncssExecuter.setEncoding( getSourceEncoding() ); // in case of null value, JavaNCSS uses platform encoding, as
// expected
ncssExecuter.execute();
if ( !isTempReportGenerated() )
{
throw new MavenReportException( "Can't process temp ncss xml file." );
}
// parse the freshly generated file and write the report
NcssReportGenerator reportGenerator =
new NcssReportGenerator( getSink(), getBundle( locale ), getLog(), constructXRefLocation() );
reportGenerator.doReport( loadDocument(), lineThreshold );
}
/**
* Load the xml file generated by javancss.
*/
private Document loadDocument( File file )
throws MavenReportException
{
try
{
SAXReader saxReader = new SAXReader();
return saxReader.read( ReaderFactory.newXmlReader( file ) );
}
catch ( DocumentException de )
{
throw new MavenReportException( de.getMessage(), de );
}
catch ( IOException ioe )
{
throw new MavenReportException( ioe.getMessage(), ioe );
}
}
private Document loadDocument()
throws MavenReportException
{
return loadDocument( new File( buildOutputFileName() ) );
}
/**
* Check that the expected temporary file generated by JavaNCSS exists.
*
* @return <code>true</code> if the temporary report exists, <code>false</code> otherwise.
*/
private boolean isTempReportGenerated()
{
return new File( buildOutputFileName() ).exists();
}
/**
* @see org.apache.maven.reporting.MavenReport#canGenerateReport()
*/
public boolean canGenerateReport()
{
return ( canGenerateSingleReport() || canGenerateAggregateReport() );
}
private boolean canGenerateAggregateReport()
{
if ( project.getModules().size() == 0 )
{
// no child modules
return false;
}
if ( sourceDirectory != null && sourceDirectory.exists() )
{
// only non-source projects can aggregate
String[] sources = scanForSources();
return !( ( sources != null ) && ( sources.length > 0 ) );
}
return true;
}
private boolean canGenerateSingleReport()
{
if ( sourceDirectory == null || !sourceDirectory.exists() )
{
return false;
}
// now that we know we have a valid existing source directory
// we check if any *.java files are existing.
String[] sources = scanForSources();
return ( sources != null ) && ( sources.length > 0 );
}
/**
* gets a list of all files in the source directory.
*
* @return the list of all files in the source directory;
*/
private String[] scanForSources()
{
String[] defaultIncludes = { "**\\*.java" };
DirectoryScanner ds = new DirectoryScanner();
if ( includes == null )
{
ds.setIncludes( defaultIncludes );
}
else
{
ds.setIncludes( includes );
}
if ( excludes != null )
{
ds.setExcludes( excludes );
}
ds.setBasedir( sourceDirectory );
getLog().debug( "Scanning base directory " + sourceDirectory );
ds.scan();
int maxFiles = ds.getIncludedFiles().length;
String[] result = new String[maxFiles];
for ( int i = 0; i < maxFiles; i++ )
{
result[i] = sourceDirectory + File.separator + ds.getIncludedFiles()[i];
}
return result;
}
/**
* Build a path for the output filename.
*
* @return A String representation of the output filename.
*/
/* package */String buildOutputFileName()
{
return getXmlOutputDirectory() + File.separator + tempFileName;
}
/**
* @see org.apache.maven.reporting.MavenReport#getName(java.util.Locale)
*/
public String getName( Locale locale )
{
return getBundle( locale ).getString( "report.javancss.name" );
}
/**
* @see org.apache.maven.reporting.MavenReport#getDescription(java.util.Locale)
*/
public String getDescription( Locale locale )
{
return getBundle( locale ).getString( "report.javancss.description" );
}
/**
* @see org.apache.maven.reporting.AbstractMavenReport#getOutputDirectory()
*/
protected String getOutputDirectory()
{
return outputDirectory.getAbsolutePath();
}
protected String getXmlOutputDirectory()
{
return xmlOutputDirectory.getAbsolutePath();
}
/**
* @see org.apache.maven.reporting.AbstractMavenReport#getProject()
*/
protected MavenProject getProject()
{
return project;
}
/**
* @see org.apache.maven.reporting.AbstractMavenReport#getSiteRenderer()
*/
protected SiteRenderer getSiteRenderer()
{
return siteRenderer;
}
/**
* @see org.apache.maven.reporting.MavenReport#getOutputName()
*/
public String getOutputName()
{
return OUTPUT_NAME;
}
/**
* Getter for the source directory
*
* @return the source directory as a File object.
*/
protected File getSourceDirectory()
{
return sourceDirectory;
}
// helper to retrieve the right bundle
private static ResourceBundle getBundle( Locale locale )
{
return ResourceBundle.getBundle( "javancss-report", locale, NcssReportMojo.class.getClassLoader() );
}
// blatantly copied from maven pmd plugin
protected String constructXRefLocation()
{
String location = null;
if ( linkXRef )
{
String relativePath =
PathTool.getRelativePath( outputDirectory.getAbsolutePath(), xrefLocation.getAbsolutePath() );
if ( StringUtils.isEmpty( relativePath ) )
{
relativePath = ".";
}
relativePath = relativePath + "/" + xrefLocation.getName();
if ( xrefLocation.exists() )
{
// XRef was already generated by manual execution of a lifecycle binding
location = relativePath;
}
else
{
// Not yet generated - check if the report is on its way
for ( Iterator reports = project.getReportPlugins().iterator(); reports.hasNext(); )
{
ReportPlugin plugin = (ReportPlugin) reports.next();
String artifactId = plugin.getArtifactId();
if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
{
location = relativePath;
}
}
}
if ( location == null )
{
getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
}
}
return location;
}
}