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