blob: 35c192efe8ba6edd4f7f55f4bb7e240d23314bc0 [file] [log] [blame]
package org.apache.maven.plugin.jxr;
/*
* 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.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.jxr.JXR;
import org.apache.maven.jxr.JavaCodeTransform;
import org.apache.maven.jxr.JxrException;
import org.apache.maven.jxr.pacman.FileManager;
import org.apache.maven.jxr.pacman.PackageManager;
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.codehaus.plexus.languages.java.version.JavaVersion;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
/**
* Base class for the JXR reports.
*
* @author <a href="mailto:bellingard.NO-SPAM@gmail.com">Fabrice Bellingard</a>
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
* @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
* @version $Id$
*/
public abstract class AbstractJxrReport
extends AbstractMavenReport
{
@Parameter( defaultValue = "${session}", readonly = true, required = true )
private MavenSession session;
/**
* Title of window of the Xref HTML files.
*/
@Parameter( defaultValue = "${project.name} ${project.version} Reference" )
private String windowTitle;
/**
* Title of main page of the Xref HTML files.
*/
@Parameter( defaultValue = "${project.name} ${project.version} Reference" )
private String docTitle;
// CHECKSTYLE_OFF: LineLength
/**
* String used at the bottom of the Xref HTML files.
*/
@Parameter( property = "bottom", defaultValue = "Copyright &#169; {inceptionYear}&#x2013;{currentYear} {organizationName}. All rights reserved." )
private String bottom;
// CHECKSTYLE_ON: LineLength
/**
* Directory where Velocity templates can be found to generate overviews, frames and summaries. Should not be used.
* If used, should be an absolute path, like <code>"${basedir}/myTemplates"</code>.
*/
@Parameter
private String templateDir;
/**
* Style sheet used for the Xref HTML files. Should not be used. If used, should be an absolute path, like
* <code>"${basedir}/myStyles.css"</code>.
*/
@Parameter
private String stylesheet;
/**
* A list of exclude patterns to use. By default no files are excluded.
*
* @since 2.1
*/
@Parameter
private ArrayList<String> excludes;
/**
* A list of include patterns to use. By default all .java files are included.
*
* @since 2.1
*/
@Parameter
private ArrayList<String> includes;
/**
* The projects in the reactor for aggregation report.
*/
@Parameter( defaultValue = "${reactorProjects}", readonly = true )
protected List<MavenProject> reactorProjects;
/**
* Whether to skip this execution.
*
* @since 2.3
*/
@Parameter( property = "maven.jxr.skip", defaultValue = "false" )
protected boolean skip;
/**
* Link the Javadoc from the Source XRef. Defaults to true and will link automatically if javadoc plugin is being
* used.
*/
@Parameter( defaultValue = "true" )
private boolean linkJavadoc;
/**
* Version of the Javadoc templates to use.
* The value should reflect `java.specification.version`, "1.4", "1.8", "9", "10",
* by default this system property is used.
*/
@Parameter( property = "javadocVersion" )
private String javadocVersion;
/**
* Version of the Javadoc templates to use.
*/
private JavaVersion javadocTemplatesVersion;
/**
* Compiles the list of directories which contain source files that will be included in the JXR report generation.
*
* @param sourceDirs the List of the source directories
* @return a List of the directories that will be included in the JXR report generation
*/
protected List<String> pruneSourceDirs( List<String> sourceDirs )
{
List<String> pruned = new ArrayList<>( sourceDirs.size() );
for ( String dir : sourceDirs )
{
if ( !pruned.contains( dir ) && hasSources( new File( dir ) ) )
{
pruned.add( dir );
}
}
return pruned;
}
/**
* Initialize some attributes required during the report generation
*/
protected void init()
{
// wanna know if Javadoc is being generated
// TODO: what if it is not part of the site though, and just on the command line?
if ( project.getModel().getReporting() != null )
{
for ( ReportPlugin reportPlugin : Collections.unmodifiableList(
project.getModel().getReporting().getPlugins() ) )
{
if ( "maven-javadoc-plugin".equals( reportPlugin.getArtifactId() ) )
{
break;
}
}
}
}
/**
* Checks whether the given directory contains Java files.
*
* @param dir the source directory
* @return true if the folder or one of its subfolders contains at least 1 Java file
*/
private boolean hasSources( File dir )
{
if ( dir.exists() && dir.isDirectory() )
{
for ( File currentFile : dir.listFiles() )
{
if ( currentFile.isFile() )
{
if ( currentFile.getName().endsWith( ".java" ) )
{
return true;
}
}
else
{
if ( Character.isJavaIdentifierStart( currentFile.getName().charAt( 0 ) ) // avoid .svn directory
&& hasSources( currentFile ) )
{
return true;
}
}
}
}
return false;
}
/**
* Creates the Xref for the Java files found in the given source directory and puts them in the given destination
* directory.
*
* @param locale The user locale to use for the Xref generation
* @param destinationDirectory The output folder
* @param sourceDirs The source directories
* @throws java.io.IOException
* @throws org.apache.maven.jxr.JxrException
*/
private void createXref( Locale locale, String destinationDirectory, List<String> sourceDirs )
throws IOException, JxrException
{
FileManager fileManager = new FileManager();
PackageManager packageManager = new PackageManager( fileManager );
JavaCodeTransform codeTransform = new JavaCodeTransform( packageManager, fileManager );
JXR jxr = new JXR( packageManager, codeTransform );
jxr.setDest( Paths.get( destinationDirectory ) );
jxr.setInputEncoding( getInputEncoding() );
jxr.setLocale( locale );
jxr.setOutputEncoding( getOutputEncoding() );
jxr.setRevision( "HEAD" );
jxr.setJavadocLinkDir( getJavadocLocation() );
// Set include/exclude patterns on the jxr instance
if ( excludes != null && !excludes.isEmpty() )
{
jxr.setExcludes( excludes.toArray( new String[0] ) );
}
if ( includes != null && !includes.isEmpty() )
{
jxr.setIncludes( includes.toArray( new String[0] ) );
}
// avoid winding up using Velocity in two class loaders.
ClassLoader savedTccl = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader( getClass().getClassLoader() );
jxr.xref( sourceDirs, getTemplateDir(), windowTitle, docTitle, getBottomText() );
}
finally
{
Thread.currentThread().setContextClassLoader( savedTccl );
}
// and finally copy the stylesheet
copyRequiredResources( destinationDirectory );
}
/**
* Returns the bottom text to be displayed at the lower part of the generated JXR reports.
*/
private String getBottomText()
{
int currentYear = Calendar.getInstance().get( Calendar.YEAR );
String year = String.valueOf( currentYear );
String inceptionYear = project.getInceptionYear();
String theBottom = StringUtils.replace( this.bottom, "{currentYear}", year );
if ( inceptionYear != null )
{
if ( inceptionYear.equals( year ) )
{
theBottom = StringUtils.replace( theBottom, "{inceptionYear}&#x2013;", "" );
}
else
{
theBottom = StringUtils.replace( theBottom, "{inceptionYear}", inceptionYear );
}
}
else
{
theBottom = StringUtils.replace( theBottom, "{inceptionYear}&#x2013;", "" );
}
if ( project.getOrganization() == null )
{
theBottom = StringUtils.replace( theBottom, " {organizationName}", "" );
}
else
{
if ( StringUtils.isNotEmpty( project.getOrganization().getName() ) )
{
if ( StringUtils.isNotEmpty( project.getOrganization().getUrl() ) )
{
// CHECKSTYLE_OFF: LineLength
theBottom =
StringUtils.replace( theBottom, "{organizationName}", "<a href=\""
+ project.getOrganization().getUrl() + "\">" + project.getOrganization().getName() + "</a>" );
// CHECKSTYLE_ON: LineLength
}
else
{
theBottom =
StringUtils.replace( theBottom, "{organizationName}", project.getOrganization().getName() );
}
}
else
{
theBottom = StringUtils.replace( theBottom, " {organizationName}", "" );
}
}
return theBottom;
}
/**
* Copy some required resources (like the stylesheet) to the given directory
*
* @param dir the directory to copy the resources to
*/
private void copyRequiredResources( String dir )
{
if ( StringUtils.isNotEmpty( stylesheet ) )
{
File stylesheetFile = new File( stylesheet );
File destStylesheetFile = new File( dir, "stylesheet.css" );
try
{
if ( stylesheetFile.isAbsolute() )
{
FileUtils.copyFile( stylesheetFile, destStylesheetFile );
}
else
{
URL stylesheetUrl = this.getClass().getClassLoader().getResource( stylesheet );
FileUtils.copyURLToFile( stylesheetUrl, destStylesheetFile );
}
}
catch ( IOException e )
{
getLog().warn( "An error occured while copying the stylesheet to the target directory", e );
}
}
else
{
if ( javadocTemplatesVersion.isAtLeast( "1.8" ) )
{
copyResources( dir, "jdk8/", "stylesheet.css" );
}
else if ( javadocTemplatesVersion.isAtLeast( "1.7" ) )
{
String[] jdk7Resources =
{
"stylesheet.css",
"resources/background.gif",
"resources/tab.gif",
"resources/titlebar.gif",
"resources/titlebar_end.gif"
};
copyResources( dir, "jdk7/", jdk7Resources );
}
else if ( javadocTemplatesVersion.isAtLeast( "1.6" ) )
{
copyResources( dir, "jdk6/", "stylesheet.css" );
}
else if ( javadocTemplatesVersion.isAtLeast( "1.4" ) )
{
copyResources( dir, "jdk4/", "stylesheet.css" );
}
else
{
// Fallback to the original stylesheet
copyResources( dir, "", "stylesheet.css" );
}
}
}
/**
* Copy styles and related resources to the given directory
*
* @param dir the directory to copy the resources to
* @param sourceFolder resources subfolder to copy from
* @param files names of files to copy
*/
private void copyResources( String dir, String sourceFolder, String... files )
{
try
{
for ( String file : files )
{
URL resourceUrl = this.getClass().getClassLoader().getResource( sourceFolder + file );
File destResourceFile = new File( dir, file );
FileUtils.copyURLToFile( resourceUrl, destResourceFile );
}
}
catch ( IOException e )
{
getLog().warn( "An error occured while copying the resource to the target directory", e );
}
}
@Override
protected MavenProject getProject()
{
return project;
}
protected MavenSession getSession()
{
return session;
}
/**
* Returns the correct resource bundle according to the locale
*
* @param locale the locale of the user
* @return the bundle corresponding to the locale
*/
protected ResourceBundle getBundle( Locale locale )
{
return ResourceBundle.getBundle( "jxr-report", locale, this.getClass().getClassLoader() );
}
/**
* @param sourceDirs
* @return true if the report could be generated
*/
protected boolean canGenerateReport( List<String> sourceDirs )
{
boolean canGenerate = !sourceDirs.isEmpty();
if ( isAggregate() && !project.isExecutionRoot() )
{
canGenerate = false;
}
return canGenerate;
}
@Override
protected void executeReport( Locale locale )
throws MavenReportException
{
if ( skip )
{
getLog().info( "Skipping JXR." );
return;
}
List<String> sourceDirs = constructSourceDirs();
if ( canGenerateReport( sourceDirs ) )
{
// init some attributes -- TODO (javadoc)
init();
// determine version of templates to use
setJavadocTemplatesVersion();
try
{
createXref( locale, getDestinationDirectory(), sourceDirs );
}
catch ( JxrException | IOException e )
{
throw new MavenReportException( "Error while generating the HTML source code of the project.", e );
}
}
}
/**
* Determine the templateDir to use, given javadocTemplatesVersion
*
* @return
*/
private String getTemplateDir()
{
// Check if overridden
if ( StringUtils.isEmpty( templateDir ) )
{
if ( javadocTemplatesVersion.isAtLeast( "1.8" ) )
{
return "templates/jdk8";
}
else if ( javadocTemplatesVersion.isAtLeast( "1.7" ) )
{
return "templates/jdk7";
}
else if ( javadocTemplatesVersion.isAtLeast( "1.4" ) )
{
return "templates/jdk4";
}
else
{
getLog().warn( "Unsupported javadocVersion: " + javadocTemplatesVersion + ". Fallback to original" );
return "templates";
}
}
// use value specified by user
return templateDir;
}
/**
* Set a new value for <code>javadocTemplatesVersion</code>
*/
private void setJavadocTemplatesVersion()
{
JavaVersion javaVersion = JavaVersion.JAVA_SPECIFICATION_VERSION;
if ( StringUtils.isNotEmpty( javadocVersion ) )
{
javadocTemplatesVersion = JavaVersion.parse( javadocVersion );
}
else
{
javadocTemplatesVersion = javaVersion;
}
}
/**
* Gets the list of the source directories to be included in the JXR report generation
*
* @return a List of the source directories whose contents will be included in the JXR report generation
*/
protected List<String> constructSourceDirs()
{
List<String> sourceDirs = new ArrayList<>( getSourceRoots() );
if ( isAggregate() )
{
for ( MavenProject project : reactorProjects )
{
if ( "java".equals( project.getArtifact().getArtifactHandler().getLanguage() ) )
{
sourceDirs.addAll( getSourceRoots( project ) );
}
}
}
sourceDirs = pruneSourceDirs( sourceDirs );
return sourceDirs;
}
@Override
public boolean canGenerateReport()
{
return canGenerateReport( constructSourceDirs() );
}
@Override
public boolean isExternalReport()
{
return true;
}
/**
* @return a String that contains the location of the javadocs
*/
private Path getJavadocLocation()
throws IOException
{
Path location = null;
if ( linkJavadoc )
{
// We don't need to do the whole translation thing like normal, because JXR does it internally.
// It probably shouldn't.
if ( getJavadocDir().exists() )
{
// XRef was already generated by manual execution of a lifecycle binding
location = getJavadocDir().toPath().toAbsolutePath();
}
else
{
// Not yet generated - check if the report is on its way
// Special case: using the site:stage goal
String stagingDirectory = System.getProperty( "stagingDirectory" );
if ( StringUtils.isNotEmpty( stagingDirectory ) )
{
String javadocDestDir = getJavadocDir().getName();
boolean javadocAggregate = JxrReportUtil.isJavadocAggregated( project );
String structureProject = JxrReportUtil.getStructure( project, false );
if ( isAggregate() && javadocAggregate )
{
location = Paths.get( stagingDirectory, structureProject, javadocDestDir );
}
if ( !isAggregate() && javadocAggregate )
{
location = Paths.get( stagingDirectory, javadocDestDir );
String hierarchy = project.getName();
MavenProject parent = project.getParent();
while ( parent != null )
{
hierarchy = parent.getName();
parent = parent.getParent();
}
location = Paths.get( stagingDirectory, hierarchy, javadocDestDir );
}
if ( isAggregate() && !javadocAggregate )
{
getLog().warn( "The JXR plugin is configured to build an aggregated report at the root, "
+ "not the Javadoc plugin." );
}
if ( !isAggregate() && !javadocAggregate )
{
location = Paths.get( stagingDirectory, structureProject, javadocDestDir );
}
}
else
{
location = getJavadocDir().toPath();
}
}
if ( location == null )
{
getLog().warn( "Unable to locate Javadoc to link to - DISABLED" );
}
}
return location;
}
/**
* Abstract method that returns the target directory where the generated JXR reports will be put.
*
* @return a String that contains the target directory name
*/
protected abstract String getDestinationDirectory();
/**
* Abstract method that returns the specified source directories that will be included in the JXR report generation.
*
* @return a List of the source directories
*/
protected abstract List<String> getSourceRoots();
/**
* Abstract method that returns the compile source directories of the specified project that will be included in the
* JXR report generation
*
* @param project the MavenProject where the JXR report plugin will be executed
* @return a List of the source directories
*/
protected abstract List<String> getSourceRoots( MavenProject project );
/**
* Abstract method that returns the directory of the javadoc files.
*
* @return a File for the directory of the javadocs
*/
protected abstract File getJavadocDir();
/**
* Is the current report aggregated?
*
* @return
*/
protected boolean isAggregate()
{
return false;
}
}