Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.
*/
package org.apache.maven.plugins.javadoc;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.javadoc.options.BootclasspathArtifact;
import org.apache.maven.plugins.javadoc.options.DocletArtifact;
import org.apache.maven.plugins.javadoc.options.Group;
import org.apache.maven.plugins.javadoc.options.JavadocOptions;
import org.apache.maven.plugins.javadoc.options.JavadocPathArtifact;
import org.apache.maven.plugins.javadoc.options.OfflineLink;
import org.apache.maven.plugins.javadoc.options.ResourcesArtifact;
import org.apache.maven.plugins.javadoc.options.Tag;
import org.apache.maven.plugins.javadoc.options.Taglet;
import org.apache.maven.plugins.javadoc.options.TagletArtifact;
import org.apache.maven.plugins.javadoc.options.io.xpp3.JavadocOptionsXpp3Writer;
import org.apache.maven.plugins.javadoc.resolver.JavadocBundle;
import org.apache.maven.plugins.javadoc.resolver.ResourceResolver;
import org.apache.maven.plugins.javadoc.resolver.SourceResolverConfig;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.reporting.MavenReportException;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Settings;
import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
import org.apache.maven.shared.artifact.filter.resolve.PatternExclusionsFilter;
import org.apache.maven.shared.artifact.filter.resolve.PatternInclusionsFilter;
import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.apache.maven.wagon.PathUtils;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.codehaus.plexus.components.io.fileselectors.IncludeExcludeFileSelector;
import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
import org.codehaus.plexus.languages.java.jpms.LocationManager;
import org.codehaus.plexus.languages.java.jpms.ModuleNameSource;
import org.codehaus.plexus.languages.java.jpms.ResolvePathRequest;
import org.codehaus.plexus.languages.java.jpms.ResolvePathResult;
import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
import org.codehaus.plexus.languages.java.version.JavaVersion;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.WriterFactory;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.ArtifactTypeRegistry;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.util.filter.AndDependencyFilter;
import org.eclipse.aether.util.filter.PatternExclusionsDependencyFilter;
import org.eclipse.aether.util.filter.ScopeDependencyFilter;
import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
import static org.apache.maven.plugins.javadoc.JavadocUtil.isEmpty;
import static org.apache.maven.plugins.javadoc.JavadocUtil.isNotEmpty;
import static org.apache.maven.plugins.javadoc.JavadocUtil.toList;
import static org.apache.maven.plugins.javadoc.JavadocUtil.toRelative;
/**
* Base class with majority of Javadoc functionalities.
*
* @author Brett Porter
* @author Vincent Siveton
* @see The javadoc Command
* @since 2.0
*/
public abstract class AbstractJavadocMojo extends AbstractMojo {
/**
* Classifier used in the name of the javadoc-options XML file, and in the resources bundle
* artifact that gets attached to the project. This one is used for non-test javadocs.
*
* @see #TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER
* @since 2.7
*/
public static final String JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER = "javadoc-resources";
/**
* Classifier used in the name of the javadoc-options XML file, and in the resources bundle
* artifact that gets attached to the project. This one is used for test-javadocs.
*
* @see #JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER
* @since 2.7
*/
public static final String TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER = "test-javadoc-resources";
/**
* The Javadoc script file name when debug parameter is on, i.e. javadoc.bat or javadoc.sh
*/
protected static final String DEBUG_JAVADOC_SCRIPT_NAME = "javadoc." + (SystemUtils.IS_OS_WINDOWS ? "bat" : "sh");
/**
* The options file name in the output directory when calling:
* javadoc.exe(or .sh) @options @packages | @argfile | @files
*/
protected static final String OPTIONS_FILE_NAME = "options";
/**
* The packages file name in the output directory when calling:
* javadoc.exe(or .sh) @options @packages | @argfile | @files
*/
protected static final String PACKAGES_FILE_NAME = "packages";
/**
* The argfile file name in the output directory when calling:
* javadoc.exe(or .sh) @options @packages | @argfile | @files
*/
protected static final String ARGFILE_FILE_NAME = "argfile";
/**
* The files file name in the output directory when calling:
* javadoc.exe(or .sh) @options @packages | @argfile | @files
*/
protected static final String FILES_FILE_NAME = "files";
/**
* Default css file name, used as file name in the output directory for the temporary custom stylesheet file
* loaded from classloader resources.
*/
private static final String DEFAULT_CSS_NAME = "stylesheet.css";
private static final String PACKAGE_LIST = "package-list";
private static final String ELEMENT_LIST = "element-list";
/**
* For Javadoc options appears since Java 1.4.
* See
* What's New in Javadoc 1.4
*
* @since 2.1
*/
private static final JavaVersion SINCE_JAVADOC_1_4 = JavaVersion.parse("1.4");
/**
* For Javadoc options appears since Java 1.4.2.
* See
* What's New in Javadoc 1.4.2
*
* @since 2.1
*/
private static final JavaVersion SINCE_JAVADOC_1_4_2 = JavaVersion.parse("1.4.2");
/**
* For Javadoc options appears since Java 5.0.
* See
* What's New in Javadoc 5.0
*
* @since 2.1
*/
private static final JavaVersion SINCE_JAVADOC_1_5 = JavaVersion.parse("1.5");
/**
* For Javadoc options appears since Java 6.0.
* See
* Javadoc Technology
*
* @since 2.4
*/
private static final JavaVersion SINCE_JAVADOC_1_6 = JavaVersion.parse("1.6");
/**
* For Javadoc options appears since Java 8.0.
* See
* Javadoc Technology
*
* @since 3.0.0
*/
private static final JavaVersion SINCE_JAVADOC_1_8 = JavaVersion.parse("1.8");
/**
*
*/
private static final JavaVersion JAVA_VERSION = JavaVersion.JAVA_SPECIFICATION_VERSION;
// ----------------------------------------------------------------------
// Mojo components
// ----------------------------------------------------------------------
/**
* Archiver manager
*
* @since 2.5
*/
@Component
private ArchiverManager archiverManager;
@Component
private ResourceResolver resourceResolver;
@Component
private RepositorySystem repoSystem;
@Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true)
private RepositorySystemSession repoSession;
@Component
private ArtifactHandlerManager artifactHandlerManager;
/**
* Project builder
*
* @since 3.0
*/
@Component
private ProjectBuilder mavenProjectBuilder;
/** */
@Component
private ToolchainManager toolchainManager;
final LocationManager locationManager = new LocationManager();
// ----------------------------------------------------------------------
// Mojo parameters
// ----------------------------------------------------------------------
/**
* The current build session instance. This is used for
* toolchain manager API calls.
*/
@Parameter(defaultValue = "${session}", readonly = true, required = true)
protected MavenSession session;
/**
* The Maven Settings.
*
* @since 2.3
*/
@Parameter(defaultValue = "${settings}", readonly = true, required = true)
private Settings settings;
/**
* The Maven Project Object
*/
@Parameter(defaultValue = "${project}", readonly = true, required = true)
protected MavenProject project;
@Parameter(defaultValue = "${mojoExecution}", readonly = true)
private MojoExecution mojo;
/**
* Specify if the Javadoc plugin should operate in offline mode. If maven is run in offline
* mode (using {@code -o} or {@code --offline} on the command line), this option has no effect
* and the plugin is always in offline mode.
*
* @since 3.6.0
*/
@Parameter(property = "maven.javadoc.offline", defaultValue = "false")
private boolean offline;
/**
* Specifies the Javadoc resources directory to be included in the Javadoc (i.e. package.html, images...).
*
* Could be used in addition of docfilessubdirs parameter.
*
* See docfilessubdirs.
*
* @see #docfilessubdirs
* @since 2.1
*/
@Parameter(defaultValue = "${basedir}/src/main/javadoc")
private File javadocDirectory;
/**
* Set an additional option(s) on the command line. All input will be passed as-is to the
* {@code @options} file. You must take care of quoting and escaping. Useful for a custom doclet.
*
* @since 3.0.0
*/
@Parameter
private String[] additionalOptions;
/**
* Sets additional Javadoc options (e.g. JVM options) on the command line.
* Example:
*
* @since 2.9
* @see #additionalJOption
*/
@Parameter
private String[] additionalJOptions;
/**
* A list of artifacts containing resources which should be copied into the
* Javadoc output directory (like stylesheets, icons, etc.).
*
* Example:
*
*
* See Javadoc.
*
*
* @since 2.5
*/
@Parameter(property = "resourcesArtifacts")
private ResourcesArtifact[] resourcesArtifacts;
/**
* The projects in the reactor for aggregation report.
*/
@Parameter(property = "reactorProjects", readonly = true)
private List reactorProjects;
/**
* Set this to true to debug the Javadoc plugin. With this, javadoc.bat(or.sh),
* options, @packages or argfile files are provided in the output directory.
*
*
* @since 2.1
*/
@Parameter(property = "debug", defaultValue = "false")
private boolean debug;
/**
* Sets the absolute path of the Javadoc Tool executable to use. Since version 2.5, a mere directory specification
* is sufficient to have the plugin use "javadoc" or "javadoc.exe" respectively from this directory.
*
* @since 2.3
*/
@Parameter(property = "javadocExecutable")
private String javadocExecutable;
/**
* Version of the Javadoc Tool executable to use, ex. "1.3", "1.5".
*
* @since 2.3
*/
@Parameter(property = "javadocVersion")
private String javadocVersion;
/**
* Version of the Javadoc Tool executable to use.
*/
private JavaVersion javadocRuntimeVersion;
/**
* Specifies whether the Javadoc generation should be skipped.
*
* @since 2.5
*/
@Parameter(property = "maven.javadoc.skip", defaultValue = "false")
protected boolean skip;
/**
* Specifies if the build will fail if there are errors during javadoc execution or not.
*
* @since 2.5
*/
@Parameter(property = "maven.javadoc.failOnError", defaultValue = "true")
protected boolean failOnError;
/**
* Specifies if the build will fail if there are warning during javadoc execution or not.
*
* @since 3.0.1
*/
@Parameter(property = "maven.javadoc.failOnWarnings", defaultValue = "false")
protected boolean failOnWarnings;
/**
* Specifies to use the
*
* options provided by the Standard Doclet for a custom doclet.
*
* Example:
*
*
* @since 2.5
*/
@Parameter(property = "useStandardDocletOptions", defaultValue = "true")
protected boolean useStandardDocletOptions;
/**
* Detect the Javadoc links for all dependencies defined in the project. The detection is based on the default
* Maven conventions, i.e.: ${project.url}/apidocs.
*
* For instance, if the project has a dependency to
* Apache Commons Lang i.e.:
*
* The added Javadoc -link parameter will be http://commons.apache.org/lang/apidocs.
*
* @see #links
* @see #dependencyLinks
* @since 2.6
*/
@Parameter(property = "detectLinks", defaultValue = "false")
private boolean detectLinks;
/**
* Detect the links for all modules defined in the project.
*
* If {@code reactorProjects} is defined in a non-aggregator way, it generates default offline links
* between modules based on the defined project's urls. For instance, if a parent project has two projects
* module1 and module2, the -linkoffline will be:
*
* The added Javadoc -linkoffline parameter for module1 will be
* /absolute/path/to/module2/target/site/apidocs
*
* The added Javadoc -linkoffline parameter for module2 will be
* /absolute/path/to/module1/target/site/apidocs
*
* @see #offlineLinks
* @since 2.6
*/
@Parameter(property = "detectOfflineLinks", defaultValue = "true")
private boolean detectOfflineLinks;
/**
* Detect the Java API link for the current build, i.e. https://docs.oracle.com/javase/1.4.2/docs/api/
* for Java source 1.4.
*
* By default, the goal detects the Javadoc API link depending the value of the source
* parameter in the org.apache.maven.plugins:maven-compiler-plugin
* (defined in ${project.build.plugins} or in ${project.build.pluginManagement}),
* or try to compute it from the {@code javadocExecutable} version.
*
* @see #links
* @see #javaApiLinks
* @since 2.6
*/
@Parameter(property = "detectJavaApiLink", defaultValue = "true")
private boolean detectJavaApiLink;
/**
* Use this parameter only if if you want to override the default URLs.
*
* The key should match {@code api_x}, where {@code x} matches the Java version.
*
* For example:
*
*
api_1.5
*
https://docs.oracle.com/javase/1.5.0/docs/api/
*
api_1.8
*
https://docs.oracle.com/javase/8/docs/api/
*
api_9
*
https://docs.oracle.com/javase/9/docs/api/
*
* @since 2.6
*/
@Parameter(property = "javaApiLinks")
private Properties javaApiLinks;
/**
* Flag controlling content validation of package-list resources. If set, the content of
* package-list resources will be validated.
*
* @since 2.8
*/
@Parameter(property = "validateLinks", defaultValue = "false")
private boolean validateLinks;
// ----------------------------------------------------------------------
// Javadoc Options - all alphabetical
// ----------------------------------------------------------------------
/**
* Specifies the paths where the boot classes reside. The bootclasspath can contain multiple paths
* by separating them with a colon (:) or a semicolon (;).
* @see
* Javadoc option bootclasspath.
* @since 2.5
*/
@Parameter(property = "bootclasspath")
private String bootclasspath;
/**
* Specifies the artifacts where the boot classes reside.
*
* Example:
*
*
* See Javadoc.
*
* @see Javadoc option docletpath.
* @since 2.1
*/
@Parameter(property = "docletArtifacts")
private DocletArtifact[] docletArtifacts;
/**
* Specifies the path to the doclet starting class file (specified with the {@link #doclet} option) and
* any jar files it depends on. The docletPath can contain multiple paths by separating them with
* a colon (:) or a semicolon (;).
* @see Javadoc option docletpath.
*/
@Parameter(property = "docletPath")
private String docletPath;
/**
* Specifies the encoding name of the source files. If not specified, the encoding value will be the value of the
* file.encoding system property.
*
* Note: In 2.4, the default value was locked to ISO-8859-1 to ensure reproducing build, but
* this was reverted in 2.5.
* @see Javadoc option encoding.
*/
@Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
private String encoding;
/**
* Unconditionally excludes the specified packages and their subpackages from the list formed by
* -subpackages. Multiple packages can be separated by commas (,), colons (:)
* or semicolons (;).
*
* Wildcards work as followed:
*
*
a wildcard at the beginning should match 1 or more folders
* @see Javadoc option exclude.
*/
@Parameter(property = "excludePackageNames")
private String excludePackageNames;
/**
* Specifies the directories where extension classes reside. Separate directories in extdirs with a
* colon (:) or a semicolon (;).
* @see Javadoc option extdirs.
*/
@Parameter(property = "extdirs")
private String extdirs;
/**
* Specifies the locale that javadoc uses when generating documentation.
* @see Javadoc option locale.
*/
@Parameter(property = "locale")
private String locale;
/**
* Specifies the maximum Java heap size to be used when launching the Javadoc tool.
* JVMs refer to this property as the -Xmx parameter. Example: '512' or '512m'.
* The memory unit depends on the JVM used. The units supported could be: k, kb,
* m, mb, g, gb, t, tb.
* If no unit specified, the default unit is m.
*/
@Parameter(property = "maxmemory")
private String maxmemory;
/**
* Specifies the minimum Java heap size to be used when launching the Javadoc tool.
* JVMs refer to this property as the -Xms parameter. Example: '512' or '512m'.
* The memory unit depends on the JVM used. The units supported could be: k, kb,
* m, mb, g, gb, t, tb.
* If no unit specified, the default unit is m.
*/
@Parameter(property = "minmemory")
private String minmemory;
/**
* This option creates documentation with the appearance and functionality of documentation generated by
* Javadoc 1.1. This is no longer supported since Javadoc 1.4 (shipped with JDK 1.4)
* @see Javadoc option 1.1.
*/
@Parameter(property = "old", defaultValue = "false")
private boolean old;
/**
* Specifies that javadoc should retrieve the text for the overview documentation from the "source" file
* specified by path/filename and place it on the Overview page (overview-summary.html).
*
* Note: could be in conflict with {@link #nooverview}.
* @see Javadoc option overview.
*
*/
@Parameter(property = "overview", defaultValue = "${basedir}/src/main/javadoc/overview.html")
private File overview;
/**
* Shuts off non-error and non-warning messages, leaving only the warnings and errors appear, making them
* easier to view.
*
* Note: was a standard doclet in Java 1.4.2 (refer to bug ID
* 4714350).
*
* Since Java 5.0.
* @see Javadoc option quiet.
*/
@Parameter(property = "quiet", defaultValue = "false")
private boolean quiet;
/**
* Specifies the access level for classes and members to show in the Javadocs.
* Possible values are:
*
*
public (shows only public classes and members)
*
protected (shows only public and protected classes and members)
*
package (shows all classes and members not marked private)
* <groups>
* <group>
* <title>Core Packages</title>
* <!-- To includes java.lang, java.lang.ref,
* java.lang.reflect and only java.util
* (i.e. not java.util.jar) -->
* <packages>java.lang*:java.util</packages>
* </group>
* <group>
* <title>Extension Packages</title>
* <!-- To include javax.accessibility,
* javax.crypto, ... (among others) -->
* <packages>javax.*</packages>
* </group>
* </groups>
*
* Note: using java.lang.* for packages would omit the java.lang
* package but using java.lang* will include it.
* @see Doclet option group.
* @see Group
*/
@Parameter
private Group[] groups;
/**
* Specifies the header text to be placed at the top of each output file.
* @see Doclet option header.
*/
@Parameter(property = "header")
private String header;
/**
* Specifies the path of an alternate help file path\filename that the HELP link in the top and bottom
* navigation bars link to.
*
* Note: could be in conflict with <nohelp/>.
*
* The helpfile could be an absolute File path.
*
* Since 2.6, it could be also be a path from a resource in the current project source directories
* (i.e. src/main/java, src/main/resources or src/main/javadoc)
* or from a resource in the Javadoc plugin dependencies, for instance:
*
* Where path/to/your/resource/yourhelp-doc.html is defined in the
* groupId:artifactId:version javadoc plugin dependency.
* @see Doclet option helpfile.
*/
@Parameter(property = "helpfile")
private String helpfile;
/**
* Adds HTML meta keyword tags to the generated file for each class.
* @see Doclet option keywords.
*
* @since 2.1
*/
@Parameter(property = "keywords", defaultValue = "false")
private boolean keywords;
/**
* Creates links to existing javadoc-generated documentation of external referenced classes.
*
* Notes:
*
*
This option is ignored if the plugin is run in offline mode (using the {@code }
* setting or specifying {@code -o, --offline} or {@code -Dmaven.javadoc.offline=true} on the
* command line.
*
all given links should have a fetchable /package-list or /element-list
* (since Java 10). For instance:
*
* will be used because https://docs.oracle.com/en/java/javase/17/docs/api/element-list exists.
*
If {@link #detectLinks} is defined, the links between the project dependencies are
* automatically added.
*
If {@link #detectJavaApiLink} is defined, a Java API link, based on the Java version of the
* project's sources, will be added automatically.
*
* @see Doclet option link
*/
@Parameter(property = "links")
protected ArrayList links;
/**
* Redefine the apidoc URL for specific dependencies when using {@link #detectLinks}.
* Useful if the dependency wasn't build with Maven or when the apidocs have been moved.
*
*
* @see #detectLinks
* @since 3.3.0
*/
@Parameter
private List dependencyLinks = new ArrayList<>();
/**
* Creates an HTML version of each source file (with line numbers) and adds links to them from the standard
* HTML documentation.
* @see Doclet option linksource/a>.
*
*/
@Parameter(property = "linksource", defaultValue = "false")
private boolean linksource;
/**
* Suppress the entire comment body, including the main description and all tags, generating only declarations.
* @see Doclet option nocomment.
*/
@Parameter(property = "nocomment", defaultValue = "false")
private boolean nocomment;
/**
* Prevents the generation of any deprecated API at all in the documentation.
* @see Doclet option nodeprecated.
*
*/
@Parameter(property = "nodeprecated", defaultValue = "false")
private boolean nodeprecated;
/**
* Prevents the generation of the file containing the list of deprecated APIs (deprecated-list.html) and the
* link in the navigation bar to that page.
* @see
* Doclet option nodeprecatedlist.
*/
@Parameter(property = "nodeprecatedlist", defaultValue = "false")
private boolean nodeprecatedlist;
/**
* Omits the HELP link in the navigation bars at the top and bottom of each page of output.
*
* Note: could be in conflict with {@link #helpfile}.
* @see Doclet option nohelp.
*/
@Parameter(property = "nohelp", defaultValue = "false")
private boolean nohelp;
/**
* Omits the index from the generated docs.
*
* Note: could be in conflict with {@link #splitindex}
* @see Doclet option noindex.
*/
@Parameter(property = "noindex", defaultValue = "false")
private boolean noindex;
/**
* Omits the navigation bar from the generated docs.
* @see Doclet option nonavbar.
*/
@Parameter(property = "nonavbar", defaultValue = "false")
private boolean nonavbar;
/**
* Omits the entire overview page from the generated docs.
*
* Note: could be in conflict with {@link #overview}.
*
* Standard Doclet undocumented option.
*
*
* @since 2.4
*/
@Parameter(property = "nooverview", defaultValue = "false")
private boolean nooverview;
/**
* Omits qualifying package name from ahead of class names in output.
* Example:
*
* <noqualifier>all</noqualifier>
* or
* <noqualifier>packagename1:packagename2</noqualifier>
*
* @see Doclet option noqualifier.
*/
@Parameter(property = "noqualifier")
private String noqualifier;
/**
* Omits from the generated docs the "Since" sections associated with the since tags.
* @see Doclet option nosince.
*/
@Parameter(property = "nosince", defaultValue = "false")
private boolean nosince;
/**
* Suppresses the timestamp, which is hidden in an HTML comment in the generated HTML near the top of each page.
*
* Note: If the project has the property project.build.outputTimestamp, the value
* will be overwritten to true. This way it is possible to generate reproducible javadoc jars.
* @see Doclet option notimestamp.
*
* @since 2.1
*/
@Parameter(property = "notimestamp", defaultValue = "false")
private boolean notimestamp;
/**
* Omits the class/interface hierarchy pages from the generated docs.
* @see Doclet option notree
*/
@Parameter(property = "notree", defaultValue = "false")
private boolean notree;
/**
* This option is a variation of {@link #links}; they both create links to javadoc-generated documentation
* for external referenced classes.
*
* Example:
*
*
* Note: if {@link #detectOfflineLinks} is defined, the offline links between the project modules are
* automatically added if the goal is calling in a non-aggregator way.
* @see OfflineLink.
* @see Doclet option linkoffline
*/
@Parameter(property = "offlineLinks")
private OfflineLink[] offlineLinks;
/**
* Specifies the destination directory where javadoc saves the generated HTML files.
* @see Doclet option d
*/
@Parameter(
property = "destDir",
alias = "destDir",
defaultValue = "${project.build.directory}/apidocs",
required = true)
protected File outputDirectory;
/**
* Specify the text for upper left frame.
* @see Bug Report about missing documentation
* @since 2.1
*/
@Parameter(property = "packagesheader")
private String packagesheader;
/**
* Generates compile-time warnings for missing serial tags.
* @see Doclet option serialwarn
*/
@Parameter(property = "serialwarn", defaultValue = "false")
private boolean serialwarn;
/**
* Specify the number of spaces each tab takes up in the source. If no tab is used in source, the default
* space is used.
*
* @see Doclet option sourcetab
* @since 2.1
*/
@Parameter(property = "sourcetab", alias = "linksourcetab")
private int sourcetab;
/**
* Splits the index file into multiple files, alphabetically, one file per letter, plus a file for any index
* entries that start with non-alphabetical characters.
*
* Note: could be in conflict with {@link #noindex}.
* @see Doclet option splitindex.
*/
@Parameter(property = "splitindex", defaultValue = "false")
private boolean splitindex;
/**
* Specifies whether the stylesheet to be used is the maven's javadoc stylesheet or
* java's default stylesheet when a {@link #stylesheetfile} parameter is not specified.
*
* Possible values: maven or java.
* @deprecated This is no longer evaluated, instead use {@link #addStylesheets} to customize the CSS.
*/
@Parameter(property = "stylesheet", defaultValue = "java")
@Deprecated
private String stylesheet;
/**
* Specifies the path of an alternate HTML stylesheet file.
*
* The stylesheetfile could be an absolute File path.
*
* Since 2.6, it could be also be a path from a resource in the current project source directories
* (i.e. src/main/java, src/main/resources or src/main/javadoc)
* or from a resource in the Javadoc plugin dependencies, for instance:
*
* Where path/to/your/resource/yourstylesheet.css is defined in the
* groupId:artifactId:version javadoc plugin dependency.
* @see Doclet option
* stylesheetfile.
*/
@Parameter(property = "stylesheetfile")
private String stylesheetfile;
/**
* Specifies the path of an additional HTML stylesheet file relative to the {@code javadocDirectory}
* Example:
*
* @since 3.3.0
*/
@Parameter
private String[] addStylesheets;
/**
* Specifies the class file that starts the taglet used in generating the documentation for that tag.
* @see Doclet option taglet.
*/
@Parameter(property = "taglet")
private String taglet;
/**
* Specifies the Taglet artifact containing the taglet class files (.class).
*
* Example:
*
*
* See Javadoc.
* @see Doclet option tagletpath.
* @since 2.1
*/
@Parameter(property = "tagletArtifact")
private TagletArtifact tagletArtifact;
/**
* Specifies several Taglet artifacts containing the taglet class files (.class). These taglets class names will be
* auto-detect and so no need to specify them.
*
* Example:
*
* Note: the placement should be a combinaison of Xaoptcmf letters:
*
*
X (disable tag)
*
a (all)
*
o (overview)
*
p (packages)
*
t (types, that is classes and interfaces)
*
c (constructors)
*
m (methods)
*
f (fields)
*
* See Javadoc.
* @see Doclet option tag.
*/
@Parameter(property = "tags")
private Tag[] tags;
/**
* Specifies the top text to be placed at the top of each output file.
* @see Java Bug 6227616.
* @see Doclet option top.
* @since 2.4
*/
@Parameter(property = "top")
private String top;
/**
* Includes one "Use" page for each documented class and package.
* @see Doclet option use.
*/
@Parameter(property = "use", defaultValue = "true")
private boolean use;
/**
* Includes the given version text in the generated docs.
* @see Doclet option version.
*/
@Parameter(property = "version", defaultValue = "true")
private boolean version;
/**
* Specifies the title to be placed in the HTML title tag.
* @see Doclet option windowtitle.
*/
@Parameter(property = "windowtitle", defaultValue = "${project.name} ${project.version} API")
private String windowtitle;
/**
* Whether dependency -sources jars should be resolved and included as source paths for javadoc generation.
* This is useful when creating javadocs for a distribution project.
*
* @since 2.7
*/
@Parameter(defaultValue = "false")
private boolean includeDependencySources;
/**
* Directory where unpacked project sources / test-sources should be cached.
*
* @see #includeDependencySources
* @since 2.7
*/
@Parameter(defaultValue = "${project.build.directory}/distro-javadoc-sources")
private File sourceDependencyCacheDir;
/**
* Whether to include transitive dependencies in the list of dependency -sources jars to include in this javadoc
* run.
*
* @see #includeDependencySources
* @since 2.7
* @deprecated if these sources depend on transitive dependencies, those dependencies should be added to the pom as
* direct dependencies
*/
@Parameter(defaultValue = "false")
@Deprecated
private boolean includeTransitiveDependencySources;
/**
* List of included dependency-source patterns. Example: org.apache.maven:*
*
* @see #includeDependencySources
* @since 2.7
*/
@Parameter
private List dependencySourceIncludes;
/**
* List of excluded dependency-source patterns. Example: org.apache.maven.shared:*
*
* @see #includeDependencySources
* @since 2.7
*/
@Parameter
private List dependencySourceExcludes;
/**
* Directory into which assembled {@link JavadocOptions} instances will be written before they
* are added to javadoc resources bundles.
*
* @since 2.7
*/
@Parameter(defaultValue = "${project.build.directory}/javadoc-bundle-options", readonly = true)
private File javadocOptionsDir;
/**
* Transient variable to allow lazy-resolution of javadoc bundles from dependencies, so they can
* be used at various points in the javadoc generation process.
*
* @since 2.7
*/
private transient List dependencyJavadocBundles;
/**
* Capability to add additional dependencies to the javadoc classpath.
* Example:
*
*
* @since 2.8.1
*/
@Parameter
private List additionalDependencies;
/**
* Include filters on the source files. Default is **\/\*.java.
* These are ignored if you specify subpackages or subpackage excludes.
*
* @since 2.9
*/
@Parameter
private List sourceFileIncludes;
/**
* exclude filters on the source files.
* These are ignored if you specify subpackages or subpackage excludes.
*
* @since 2.9
*/
@Parameter
private List sourceFileExcludes;
/**
* To apply a security fix on generated javadoc, see
*
* Allow for configuration of the javadoc tool via maven toolchains.
* This overrules the toolchain selected by the maven-toolchain-plugin.
*
*
*
* Comma separated list of modules (artifactId) to not add in aggregated javadoc
*
*
* @since 3.2.0
*/
@Parameter(property = "maven.javadoc.skippedModules")
private String skippedModules;
/**
* Timestamp for reproducible output archive entries, either formatted as ISO 8601
* yyyy-MM-dd'T'HH:mm:ssXXX or as an int representing seconds since the epoch (like
* SOURCE_DATE_EPOCH).
*
* @since 3.2.0
*/
@Parameter(defaultValue = "${project.build.outputTimestamp}")
protected String outputTimestamp;
// ----------------------------------------------------------------------
// protected methods
// ----------------------------------------------------------------------
/**
* Indicates whether this goal is flagged with @aggregator.
*
* @return true if the goal is designed as an aggregator, false otherwise.
* @see AggregatorJavadocReport
* @see AggregatorTestJavadocReport
*/
protected boolean isAggregator() {
return false;
}
/**
* Indicates whether this goal generates documentation for the Java Test code.
*
* @return true if the goal generates Test Javadocs, false otherwise.
*/
protected boolean isTest() {
return false;
}
/**
* @return the output directory
*/
protected String getOutputDirectory() {
return outputDirectory.getAbsoluteFile().toString();
}
protected MavenProject getProject() {
return project;
}
/**
* @param p not null maven project
* @return the list of directories where compiled classes are placed for the given project. These dirs are
* added to the javadoc classpath.
*/
protected List getProjectBuildOutputDirs(MavenProject p) {
if (StringUtils.isEmpty(p.getBuild().getOutputDirectory())) {
return Collections.emptyList();
}
return Collections.singletonList(new File(p.getBuild().getOutputDirectory()));
}
/**
* @param project the project in which to find a classes file
* @return null, the attached artifact file, or outputDirectory.
*/
protected File getClassesFile(MavenProject project) {
if (!isAggregator() && isTest()) {
return null;
}
if (project.getArtifact() != null && project.getArtifact().getFile() != null) {
File artifactFile = project.getArtifact().getFile();
if (artifactFile.isDirectory() || artifactFile.getName().endsWith(".jar")) {
return artifactFile;
}
} else if (project.getExecutionProject() != null
&& project.getExecutionProject().getArtifact() != null
&& project.getExecutionProject().getArtifact().getFile() != null) {
File artifactFile = project.getExecutionProject().getArtifact().getFile();
if (artifactFile.isDirectory() || artifactFile.getName().endsWith(".jar")) {
return artifactFile;
}
}
if (project.getBuild().getOutputDirectory() != null) {
return new File(project.getBuild().getOutputDirectory());
} else {
return null;
}
}
/**
* @param p not null maven project
* @return the list of source paths for the given project
*/
protected List getProjectSourceRoots(MavenProject p) {
if ("pom".equals(p.getPackaging().toLowerCase())) {
return Collections.emptyList();
}
return p.getCompileSourceRoots() == null
? Collections.emptyList()
: new LinkedList<>(p.getCompileSourceRoots());
}
/**
* @param p not null maven project
* @return the list of source paths for the execution project of the given project
*/
protected List getExecutionProjectSourceRoots(MavenProject p) {
if ("pom".equals(p.getExecutionProject().getPackaging().toLowerCase())) {
return Collections.emptyList();
}
return p.getExecutionProject().getCompileSourceRoots() == null
? Collections.emptyList()
: new LinkedList<>(p.getExecutionProject().getCompileSourceRoots());
}
/**
* @return the current javadoc directory
*/
protected File getJavadocDirectory() {
return javadocDirectory;
}
/**
* @return the doclint specific checks configuration
*/
protected String getDoclint() {
return doclint;
}
/**
* @return the title to be placed near the top of the overview summary file
*/
protected String getDoctitle() {
return doctitle;
}
/**
* @return the overview documentation file from the user parameter or from the javadocdirectory
*/
protected File getOverview() {
return overview;
}
/**
* @return the title to be placed in the HTML title tag
*/
protected String getWindowtitle() {
return windowtitle;
}
/**
* @return the charset attribute or the value of {@link #getDocencoding()} if null.
*/
private String getCharset() {
return (charset == null || charset.isEmpty()) ? getDocencoding() : charset;
}
/**
* @return the docencoding attribute or UTF-8 if null.
*/
private String getDocencoding() {
return (docencoding == null || docencoding.isEmpty()) ? ReaderFactory.UTF_8 : docencoding;
}
/**
* @return the encoding attribute or the value of file.encoding system property if null.
*/
private String getEncoding() {
return (encoding == null || encoding.isEmpty()) ? ReaderFactory.FILE_ENCODING : encoding;
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
verifyRemovedParameter("aggregator");
verifyRemovedParameter("proxyHost");
verifyRemovedParameter("proxyPort");
verifyReplacedParameter("additionalparam", "additionalOptions");
doExecute();
}
abstract void doExecute() throws MojoExecutionException, MojoFailureException;
protected final void verifyRemovedParameter(String paramName) {
Xpp3Dom configDom = mojo.getConfiguration();
if (configDom != null) {
if (configDom.getChild(paramName) != null) {
throw new IllegalArgumentException(
"parameter '" + paramName + "' has been removed from the plugin, please verify documentation.");
}
}
}
private void verifyReplacedParameter(String oldParamName, String newParamNew) {
Xpp3Dom configDom = mojo.getConfiguration();
if (configDom != null) {
if (configDom.getChild(oldParamName) != null) {
throw new IllegalArgumentException("parameter '" + oldParamName + "' has been replaced with "
+ newParamNew + ", please verify documentation.");
}
}
}
/**
* The package documentation details the
* Javadoc Options used by this Plugin.
*
* @param unusedLocale the wanted locale (actually unused).
* @throws MavenReportException if any
*/
protected void executeReport(Locale unusedLocale) throws MavenReportException {
if (skip) {
getLog().info("Skipping javadoc generation");
return;
}
if (getLog().isDebugEnabled()) {
this.debug = true;
}
// NOTE: Always generate this file, to allow javadocs from modules to be aggregated via
// useDependencySources in a distro module build.
try {
buildJavadocOptions();
} catch (IOException e) {
throw new MavenReportException("Failed to generate javadoc options file: " + e.getMessage(), e);
}
Collection sourcePaths = getSourcePaths();
Collection collectedSourcePaths =
sourcePaths.stream().flatMap(e -> e.getSourcePaths().stream()).collect(Collectors.toList());
Map> files = getFiles(collectedSourcePaths);
if (!canGenerateReport(files)) {
return;
}
// ----------------------------------------------------------------------
// Find the javadoc executable and version
// ----------------------------------------------------------------------
String jExecutable;
try {
jExecutable = getJavadocExecutable();
} catch (IOException e) {
throw new MavenReportException("Unable to find javadoc command: " + e.getMessage(), e);
}
setFJavadocVersion(new File(jExecutable));
Collection packageNames;
if (javadocRuntimeVersion.isAtLeast("9")) {
packageNames = getPackageNamesRespectingJavaModules(sourcePaths);
} else {
packageNames = getPackageNames(files);
}
// ----------------------------------------------------------------------
// Javadoc output directory as File
// ----------------------------------------------------------------------
File javadocOutputDirectory = new File(getOutputDirectory());
if (javadocOutputDirectory.exists() && !javadocOutputDirectory.isDirectory()) {
throw new MavenReportException("IOException: " + getOutputDirectory() + " is not a directory.");
}
if (javadocOutputDirectory.exists() && !javadocOutputDirectory.canWrite()) {
throw new MavenReportException("IOException: " + getOutputDirectory() + " is not writable.");
}
javadocOutputDirectory.mkdirs();
// ----------------------------------------------------------------------
// Copy all resources
// ----------------------------------------------------------------------
copyAllResources(javadocOutputDirectory);
// ----------------------------------------------------------------------
// Create command line for Javadoc
// ----------------------------------------------------------------------
Commandline cmd = new Commandline();
cmd.getShell().setQuotedArgumentsEnabled(false); // for Javadoc JVM args
cmd.setWorkingDirectory(javadocOutputDirectory.getAbsolutePath());
cmd.setExecutable(jExecutable);
// ----------------------------------------------------------------------
// Wrap Javadoc JVM args
// ----------------------------------------------------------------------
addMemoryArg(cmd, "-Xmx", this.maxmemory);
addMemoryArg(cmd, "-Xms", this.minmemory);
addProxyArg(cmd);
if (additionalJOption != null && !additionalJOption.isEmpty()) {
cmd.createArg().setValue(additionalJOption);
}
if (additionalJOptions != null && additionalJOptions.length != 0) {
for (String jo : additionalJOptions) {
cmd.createArg().setValue(jo);
}
}
// ----------------------------------------------------------------------
// Wrap Standard doclet Options
// ----------------------------------------------------------------------
List standardDocletArguments = new ArrayList<>();
Set offlineLinks;
if ((doclet == null || doclet.isEmpty()) || useStandardDocletOptions) {
offlineLinks = getLinkofflines();
addStandardDocletOptions(javadocOutputDirectory, standardDocletArguments, offlineLinks);
} else {
offlineLinks = Collections.emptySet();
}
// ----------------------------------------------------------------------
// Wrap Javadoc options
// ----------------------------------------------------------------------
List javadocArguments = new ArrayList<>();
addJavadocOptions(javadocOutputDirectory, javadocArguments, sourcePaths, offlineLinks);
// ----------------------------------------------------------------------
// Write options file and include it in the command line
// ----------------------------------------------------------------------
List arguments = new ArrayList<>(javadocArguments.size() + standardDocletArguments.size());
arguments.addAll(javadocArguments);
arguments.addAll(standardDocletArguments);
if (arguments.size() > 0) {
addCommandLineOptions(cmd, arguments, javadocOutputDirectory);
}
// ----------------------------------------------------------------------
// Write packages file and include it in the command line
// ----------------------------------------------------------------------
// MJAVADOC-365 if includes/excludes are specified, these take precedence over the default
// package-based mode and force javadoc into file-based mode unless subpackages are
// specified. Subpackages take precedence over file-based include/excludes. Why? Because
// getFiles(...) returns an empty list when subpackages are specified.
boolean includesExcludesActive = (sourceFileIncludes != null && !sourceFileIncludes.isEmpty())
|| (sourceFileExcludes != null && !sourceFileExcludes.isEmpty());
if (includesExcludesActive && !(subpackages == null || subpackages.isEmpty())) {
getLog().warn("sourceFileIncludes and sourceFileExcludes have no effect when subpackages are specified!");
includesExcludesActive = false;
}
if (!packageNames.isEmpty() && !includesExcludesActive) {
addCommandLinePackages(cmd, javadocOutputDirectory, packageNames);
// ----------------------------------------------------------------------
// Write argfile file and include it in the command line
// ----------------------------------------------------------------------
List specialFiles = getSpecialFiles(files);
if (!specialFiles.isEmpty()) {
addCommandLineArgFile(cmd, javadocOutputDirectory, specialFiles);
}
} else {
// ----------------------------------------------------------------------
// Write argfile file and include it in the command line
// ----------------------------------------------------------------------
List allFiles = new ArrayList<>();
for (Map.Entry> filesEntry : files.entrySet()) {
for (String file : filesEntry.getValue()) {
allFiles.add(filesEntry.getKey().resolve(file).toString());
}
}
if (!files.isEmpty()) {
addCommandLineArgFile(cmd, javadocOutputDirectory, allFiles);
}
}
// ----------------------------------------------------------------------
// Execute command line
// ----------------------------------------------------------------------
executeJavadocCommandLine(cmd, javadocOutputDirectory);
// delete generated javadoc files only if no error and no debug mode
// [MJAVADOC-336] Use File.delete() instead of File.deleteOnExit() to
// prevent these files from making their way into archives.
if (!debug) {
for (int i = 0; i < cmd.getArguments().length; i++) {
String arg = cmd.getArguments()[i].trim();
if (!arg.startsWith("@")) {
continue;
}
File argFile = new File(javadocOutputDirectory, arg.substring(1));
if (argFile.exists()) {
argFile.delete();
}
}
File scriptFile = new File(javadocOutputDirectory, DEBUG_JAVADOC_SCRIPT_NAME);
if (scriptFile.exists()) {
scriptFile.delete();
}
}
if (applyJavadocSecurityFix) {
// finally, patch the Javadoc vulnerability in older Javadoc tools (CVE-2013-1571):
try {
final int patched = fixFrameInjectionBug(javadocOutputDirectory, getDocencoding());
if (patched > 0) {
getLog().info(String.format(
"Fixed Javadoc frame injection vulnerability (CVE-2013-1571) in %d files.", patched));
}
} catch (IOException e) {
throw new MavenReportException("Failed to patch javadocs vulnerability: " + e.getMessage(), e);
}
} else {
getLog().info("applying javadoc security fix has been disabled");
}
}
/**
* Method to get the files on the specified source paths
*
* @param sourcePaths a Collection that contains the paths to the source files
* @return a List that contains the specific path for every source file
* @throws MavenReportException {@link MavenReportException} issue while generating report
*/
protected Map> getFiles(Collection sourcePaths) throws MavenReportException {
Map> mappedFiles = new LinkedHashMap<>(sourcePaths.size());
if (subpackages == null || subpackages.isEmpty()) {
Collection excludedPackages = getExcludedPackages();
final boolean autoExclude;
if (release != null) {
autoExclude = JavaVersion.parse(release).isBefore("9");
} else if (source != null) {
autoExclude = JavaVersion.parse(source).isBefore("9");
} else {
// if legacy mode is active, treat it like pre-Java 9 (exclude module-info),
// otherwise don't auto-exclude anything.
autoExclude = legacyMode;
}
for (Path sourcePath : sourcePaths) {
File sourceDirectory = sourcePath.toFile();
List files = new ArrayList<>(JavadocUtil.getFilesFromSource(
sourceDirectory, sourceFileIncludes, sourceFileExcludes, excludedPackages));
if (autoExclude && files.remove("module-info.java")) {
getLog().debug("Auto exclude module-info.java due to source value");
}
mappedFiles.put(sourcePath, files);
}
}
return mappedFiles;
}
/**
* Method to get the source paths per reactorProject. If no source path is specified in the parameter, the compile
* source roots of the project will be used.
*
* @return a Map of the project absolute source paths per projects key (G:A)
* @throws MavenReportException {@link MavenReportException} issue while generating report
* @see JavadocUtil#pruneDirs(MavenProject, Collection)
*/
protected Collection getSourcePaths() throws MavenReportException {
Collection mappedSourcePaths = new ArrayList<>();
if (sourcepath == null || sourcepath.isEmpty()) {
if (!"pom".equals(project.getPackaging())) {
Set sourcePaths =
new LinkedHashSet<>(JavadocUtil.pruneDirs(project, getProjectSourceRoots(project)));
if (project.getExecutionProject() != null) {
sourcePaths.addAll(JavadocUtil.pruneDirs(project, getExecutionProjectSourceRoots(project)));
}
/*
* Should be after the source path (i.e. -sourcepath '.../src/main/java;.../src/main/javadoc') and *not*
* the opposite. If not, the javadoc tool always copies doc files, even if -docfilessubdirs is not
* setted.
*/
if (getJavadocDirectory() != null) {
File javadocDir = getJavadocDirectory();
if (javadocDir.exists() && javadocDir.isDirectory()) {
Collection l = JavadocUtil.pruneDirs(
project,
Collections.singletonList(getJavadocDirectory().getAbsolutePath()));
sourcePaths.addAll(l);
}
}
if (!sourcePaths.isEmpty()) {
mappedSourcePaths.add(buildJavadocModule(project, sourcePaths));
}
}
if (isAggregator()) {
for (MavenProject subProject : getAggregatedProjects()) {
if (subProject != project) {
Collection additionalSourcePaths = new ArrayList<>();
List sourceRoots = getProjectSourceRoots(subProject);
if (subProject.getExecutionProject() != null) {
sourceRoots.addAll(getExecutionProjectSourceRoots(subProject));
}
ArtifactHandler artifactHandler =
subProject.getArtifact().getArtifactHandler();
if ("java".equals(artifactHandler.getLanguage())) {
additionalSourcePaths.addAll(JavadocUtil.pruneDirs(subProject, sourceRoots));
}
if (getJavadocDirectory() != null) {
String javadocDirRelative = PathUtils.toRelative(
project.getBasedir(), getJavadocDirectory().getAbsolutePath());
File javadocDir = new File(subProject.getBasedir(), javadocDirRelative);
if (javadocDir.exists() && javadocDir.isDirectory()) {
Collection l = JavadocUtil.pruneDirs(
subProject, Collections.singletonList(javadocDir.getAbsolutePath()));
additionalSourcePaths.addAll(l);
}
}
if (!additionalSourcePaths.isEmpty()) {
mappedSourcePaths.add(buildJavadocModule(subProject, additionalSourcePaths));
}
}
}
}
if (includeDependencySources) {
mappedSourcePaths.addAll(getDependencySourcePaths());
}
} else {
Collection sourcePaths =
JavadocUtil.pruneDirs(project, new ArrayList<>(Arrays.asList(JavadocUtil.splitPath(sourcepath))));
if (getJavadocDirectory() != null) {
Collection l = JavadocUtil.pruneDirs(
project, Collections.singletonList(getJavadocDirectory().getAbsolutePath()));
sourcePaths.addAll(l);
}
if (!sourcePaths.isEmpty()) {
mappedSourcePaths.add(new JavadocModule(
ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId()),
getClassesFile(project),
sourcePaths));
}
}
return mappedSourcePaths;
}
private JavadocModule buildJavadocModule(MavenProject project, Collection sourcePaths) {
File classessFile = getClassesFile(project);
ResolvePathResult resolvePathResult = getResolvePathResult(classessFile);
if (resolvePathResult == null) {
return new JavadocModule(
ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId()),
classessFile,
sourcePaths);
} else {
return new JavadocModule(
ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId()),
classessFile,
sourcePaths,
resolvePathResult.getModuleDescriptor(),
resolvePathResult.getModuleNameSource());
}
}
/**
* Recursively add the modules of the aggregatedProject to the set of aggregatedModules.
*
* @param aggregatedProject the project being aggregated
* @param reactorProjectsMap map of (still) available reactor projects
*/
private Set modulesForAggregatedProject(
MavenProject aggregatedProject, Map reactorProjectsMap) {
// Maven does not supply an easy way to get the projects representing
// the modules of a project. So we will get the paths to the base
// directories of the modules from the project and compare with the
// base directories of the projects in the reactor.
if (aggregatedProject.getModules().isEmpty()) {
return Collections.singleton(aggregatedProject);
}
Path basePath = aggregatedProject.getBasedir().toPath();
List modulePaths = new LinkedList<>();
for (String module : aggregatedProject.getModules()) {
modulePaths.add(basePath.resolve(module).normalize());
}
Set aggregatedModules = new LinkedHashSet<>();
for (Path modulePath : modulePaths) {
MavenProject module = reactorProjectsMap.remove(modulePath);
if (module != null) {
aggregatedModules.addAll(modulesForAggregatedProject(module, reactorProjectsMap));
}
}
return aggregatedModules;
}
/**
* Override this method to customize the configuration for resolving dependency sources. The default
* behavior enables the resolution of -sources jar files.
* @param config {@link SourceResolverConfig}
* @return {@link SourceResolverConfig}
*/
protected SourceResolverConfig configureDependencySourceResolution(final SourceResolverConfig config) {
return config.withCompileSources();
}
/**
* Resolve dependency sources so they can be included directly in the javadoc process. To customize this,
* override {@link AbstractJavadocMojo#configureDependencySourceResolution(SourceResolverConfig)}.
* @return List of source paths.
* @throws MavenReportException {@link MavenReportException}
*/
protected final Collection getDependencySourcePaths() throws MavenReportException {
try {
if (sourceDependencyCacheDir.exists()) {
FileUtils.forceDelete(sourceDependencyCacheDir);
sourceDependencyCacheDir.mkdirs();
}
} catch (IOException e) {
throw new MavenReportException(
"Failed to delete cache directory: " + sourceDependencyCacheDir + "\nReason: " + e.getMessage(), e);
}
final SourceResolverConfig config = getDependencySourceResolverConfig();
try {
return resourceResolver.resolveDependencySourcePaths(config);
} catch (org.apache.maven.artifact.resolver.ArtifactResolutionException
| org.apache.maven.artifact.resolver.ArtifactNotFoundException e) {
throw new MavenReportException(
"Failed to resolve one or more javadoc source/resource artifacts:\n\n" + e.getMessage(), e);
}
}
/**
* Returns a ArtifactFilter that only includes direct dependencies of this project
* (verified via groupId and artifactId).
*
* @return
*/
private TransformableFilter createDependencyArtifactFilter() {
Set dependencyArtifacts = project.getDependencyArtifacts();
List artifactPatterns = new ArrayList<>(dependencyArtifacts.size());
for (Artifact artifact : dependencyArtifacts) {
artifactPatterns.add(artifact.getGroupId() + ":" + artifact.getArtifactId());
}
return new PatternInclusionsFilter(artifactPatterns);
}
/**
* Construct a SourceResolverConfig for resolving dependency sources and resources in a consistent
* way, so it can be reused for both source and resource resolution.
*
* @since 2.7
*/
private SourceResolverConfig getDependencySourceResolverConfig() {
final List andFilters = new ArrayList<>();
final List dependencyIncludes = dependencySourceIncludes;
final List dependencyExcludes = dependencySourceExcludes;
if (!includeTransitiveDependencySources || isNotEmpty(dependencyIncludes) || isNotEmpty(dependencyExcludes)) {
if (!includeTransitiveDependencySources) {
andFilters.add(createDependencyArtifactFilter());
}
if (isNotEmpty(dependencyIncludes)) {
andFilters.add(new PatternInclusionsFilter(dependencyIncludes));
}
if (isNotEmpty(dependencyExcludes)) {
andFilters.add(new PatternExclusionsFilter(dependencyExcludes));
}
}
return configureDependencySourceResolution(
new SourceResolverConfig(project, getProjectBuildingRequest(project), sourceDependencyCacheDir)
.withReactorProjects(this.reactorProjects))
.withFilter(new AndFilter(andFilters));
}
private ProjectBuildingRequest getProjectBuildingRequest(MavenProject currentProject) {
return new DefaultProjectBuildingRequest(session.getProjectBuildingRequest())
.setRemoteRepositories(currentProject.getRemoteArtifactRepositories());
}
/**
* Method that indicates whether the javadoc can be generated or not. If the project does not contain any source
* files and no subpackages are specified, the plugin will terminate.
*
* @param files the project files
* @return a boolean that indicates whether javadoc report can be generated or not
*/
protected boolean canGenerateReport(Map> files) {
for (Collection filesValues : files.values()) {
if (!filesValues.isEmpty()) {
return true;
}
}
return !(subpackages == null || subpackages.isEmpty());
}
// ----------------------------------------------------------------------
// private methods
// ----------------------------------------------------------------------
/**
* Method to get the excluded source files from the javadoc and create the argument string
* that will be included in the javadoc commandline execution.
*
* @param sourcePaths the collection of paths to the source files
* @return a String that contains the exclude argument that will be used by javadoc
* @throws MavenReportException
*/
private String getExcludedPackages(Collection sourcePaths) throws MavenReportException {
List excludedNames = null;
if ((sourcepath != null && !sourcepath.isEmpty()) && (subpackages != null && !subpackages.isEmpty())) {
Collection excludedPackages = getExcludedPackages();
excludedNames = JavadocUtil.getExcludedPackages(sourcePaths, excludedPackages);
}
String excludeArg = "";
if ((subpackages != null && !subpackages.isEmpty()) && excludedNames != null) {
// add the excludedpackage names
excludeArg = StringUtils.join(excludedNames.iterator(), ":");
}
return excludeArg;
}
/**
* Method to format the specified source paths that will be accepted by the javadoc tool.
*
* @param sourcePaths the list of paths to the source files that will be included in the javadoc.
* @return a String that contains the formatted source path argument, separated by the System pathSeparator
* string (colon (:) on Solaris or semicolon (;) on Windows).
* @see File#pathSeparator
*/
private String getSourcePath(Collection sourcePaths) {
String sourcePath = null;
if ((subpackages == null || subpackages.isEmpty()) || (sourcepath != null && !sourcepath.isEmpty())) {
sourcePath = StringUtils.join(sourcePaths.iterator(), File.pathSeparator);
}
return sourcePath;
}
/**
* Method to get the packages specified in the excludePackageNames parameter. The packages are split
* with ',', ':', or ';' and then formatted.
*
* @return an array of String objects that contain the package names
* @throws MavenReportException
*/
private Collection getExcludedPackages() throws MavenReportException {
Set excluded = new LinkedHashSet<>();
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getExcludePackageNames())) {
excluded.addAll(options.getExcludePackageNames());
}
}
}
}
// for the specified excludePackageNames
if (excludePackageNames != null && !excludePackageNames.isEmpty()) {
List packageNames = Arrays.asList(excludePackageNames.split("[,:;]"));
excluded.addAll(trimValues(packageNames));
}
return excluded;
}
private static List trimValues(List items) {
List result = new ArrayList<>(items.size());
for (String item : items) {
String trimmed = item.trim();
if (trimmed == null || trimmed.isEmpty()) {
continue;
}
result.add(trimmed);
}
return result;
}
private List toResolverDependencies(List dependencies) {
if (dependencies == null) {
return null;
}
ArtifactTypeRegistry registry = RepositoryUtils.newArtifactTypeRegistry(artifactHandlerManager);
return dependencies.stream()
.map(d -> RepositoryUtils.toDependency(d, registry))
.collect(Collectors.toList());
}
/**
* Method that gets the classpath and modulepath elements that will be specified in the javadoc
* -classpath and --module-path parameter.
* Since we have all the sources of the current reactor, it is sufficient to consider the
* dependencies of the reactor modules, excluding the module artifacts which may not yet be available
* when the reactor project is built for the first time.
*
* @return all classpath elements
* @throws MavenReportException if any.
*/
private Collection getPathElements() throws MavenReportException {
Set classpathElements = new LinkedHashSet<>();
Map compileArtifactMap = new LinkedHashMap<>();
if (isTest()) {
classpathElements.addAll(getProjectBuildOutputDirs(project));
}
populateCompileArtifactMap(compileArtifactMap, project.getArtifacts());
if (isAggregator()) {
Collection aggregatorProjects = getAggregatedProjects();
List reactorArtifacts = new ArrayList<>();
for (MavenProject p : aggregatorProjects) {
reactorArtifacts.add(p.getGroupId() + ':' + p.getArtifactId());
}
DependencyFilter dependencyFilter = new AndDependencyFilter(
new PatternExclusionsDependencyFilter(reactorArtifacts), getDependencyScopeFilter());
for (MavenProject subProject : aggregatorProjects) {
if (subProject != project) {
File projectArtifactFile = getClassesFile(subProject);
if (projectArtifactFile != null) {
classpathElements.add(projectArtifactFile);
} else {
classpathElements.addAll(getProjectBuildOutputDirs(subProject));
}
try {
StringBuilder sb = new StringBuilder();
sb.append("Compiled artifacts for ");
sb.append(subProject.getGroupId()).append(":");
sb.append(subProject.getArtifactId()).append(":");
sb.append(subProject.getVersion()).append('\n');
List managedDependencies = null;
if (subProject.getDependencyManagement() != null) {
managedDependencies =
subProject.getDependencyManagement().getDependencies();
}
CollectRequest collRequest = new CollectRequest(
toResolverDependencies(subProject.getDependencies()),
toResolverDependencies(managedDependencies),
subProject.getRemoteProjectRepositories());
DependencyRequest depRequest = new DependencyRequest(collRequest, dependencyFilter);
for (ArtifactResult artifactResult : repoSystem
.resolveDependencies(repoSession, depRequest)
.getArtifactResults()) {
List artifacts =
Collections.singletonList(RepositoryUtils.toArtifact(artifactResult.getArtifact()));
populateCompileArtifactMap(compileArtifactMap, artifacts);
sb.append(artifactResult.getArtifact().getFile()).append('\n');
}
if (getLog().isDebugEnabled()) {
getLog().debug(sb.toString());
}
} catch (DependencyResolutionException e) {
throw new MavenReportException(e.getMessage(), e);
}
}
}
}
for (Artifact a : compileArtifactMap.values()) {
classpathElements.add(a.getFile());
}
if (additionalDependencies != null) {
for (Dependency dependency : additionalDependencies) {
Artifact artifact = resolveDependency(dependency);
getLog().debug("add additional artifact with path " + artifact.getFile());
classpathElements.add(artifact.getFile());
}
}
return classpathElements;
}
protected ScopeDependencyFilter getDependencyScopeFilter() {
return new ScopeDependencyFilter(
Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_SYSTEM), null);
}
/**
* @param dependency {@link Dependency}
* @return {@link Artifact}
* @throws MavenReportException when artifact could not be resolved
*/
public Artifact resolveDependency(Dependency dependency) throws MavenReportException {
ArtifactTypeRegistry registry = RepositoryUtils.newArtifactTypeRegistry(artifactHandlerManager);
ArtifactRequest req = new ArtifactRequest(
RepositoryUtils.toDependency(dependency, registry).getArtifact(),
project.getRemoteProjectRepositories(),
null);
try {
ArtifactResult resolutionResult = repoSystem.resolveArtifact(repoSession, req);
return RepositoryUtils.toArtifact(resolutionResult.getArtifact());
} catch (ArtifactResolutionException e) {
throw new MavenReportException("artifact resolver problem - " + e.getMessage(), e);
}
}
// TODO remove the part with ToolchainManager lookup once we depend on
// 3.0.9 (have it as prerequisite). Define as regular component field then.
protected final Toolchain getToolchain() {
Toolchain tc = null;
if (jdkToolchain != null) {
// Maven 3.3.1 has plugin execution scoped Toolchain Support
try {
Method getToolchainsMethod = toolchainManager
.getClass()
.getMethod("getToolchains", MavenSession.class, String.class, Map.class);
@SuppressWarnings("unchecked")
List tcs =
(List) getToolchainsMethod.invoke(toolchainManager, session, "jdk", jdkToolchain);
if (tcs != null && tcs.size() > 0) {
tc = tcs.get(0);
}
} catch (SecurityException | ReflectiveOperationException e) {
// ignore
}
}
if (tc == null) {
tc = toolchainManager.getToolchainFromBuildContext("jdk", session);
}
return tc;
}
/**
* Method to put the artifacts in the hashmap.
*
* @param compileArtifactMap the hashmap that will contain the artifacts
* @param artifactList the list of artifacts that will be put in the map
* @throws MavenReportException if any
*/
private void populateCompileArtifactMap(Map compileArtifactMap, Collection artifactList)
throws MavenReportException {
if (artifactList == null) {
return;
}
for (Artifact newArtifact : artifactList) {
File file = newArtifact.getFile();
if (file == null) {
throw new MavenReportException(
"Error in plugin descriptor - " + "dependency was not resolved for artifact: "
+ newArtifact.getGroupId() + ":" + newArtifact.getArtifactId() + ":"
+ newArtifact.getVersion());
}
if (compileArtifactMap.get(newArtifact.getDependencyConflictId()) != null) {
Artifact oldArtifact = compileArtifactMap.get(newArtifact.getDependencyConflictId());
ArtifactVersion oldVersion = new DefaultArtifactVersion(oldArtifact.getVersion());
ArtifactVersion newVersion = new DefaultArtifactVersion(newArtifact.getVersion());
if (newVersion.compareTo(oldVersion) > 0) {
compileArtifactMap.put(newArtifact.getDependencyConflictId(), newArtifact);
}
} else {
compileArtifactMap.put(newArtifact.getDependencyConflictId(), newArtifact);
}
}
}
/**
* Method that sets the bottom text that will be displayed on the bottom of the
* javadocs.
*
* @return a String that contains the text that will be displayed at the bottom of the javadoc
*/
private String getBottomText() {
final String inceptionYear = project.getInceptionYear();
// get Reproducible Builds outputTimestamp date value or the current local date.
final LocalDate localDate = MavenArchiver.parseBuildOutputTimestamp(outputTimestamp)
.map(instant -> instant.atZone(ZoneOffset.UTC).toLocalDate())
.orElseGet(LocalDate::now);
final String currentYear = Integer.toString(localDate.getYear());
String theBottom = StringUtils.replace(this.bottom, "{currentYear}", currentYear);
if ((inceptionYear == null) || inceptionYear.equals(currentYear)) {
theBottom = StringUtils.replace(theBottom, "{inceptionYear}–", "");
} else {
theBottom = StringUtils.replace(theBottom, "{inceptionYear}", inceptionYear);
}
if (project.getOrganization() == null) {
theBottom = StringUtils.replace(theBottom, " {organizationName}", "");
} else {
if (StringUtils.isNotEmpty(project.getOrganization().getName())) {
if (StringUtils.isNotEmpty(project.getOrganization().getUrl())) {
theBottom = StringUtils.replace(
theBottom,
"{organizationName}",
""
+ project.getOrganization().getName() + "");
} else {
theBottom = StringUtils.replace(
theBottom,
"{organizationName}",
project.getOrganization().getName());
}
} else {
theBottom = StringUtils.replace(theBottom, " {organizationName}", "");
}
}
return theBottom;
}
/**
* Method to get the stylesheet path file to be used by the Javadoc Tool.
*
* If the {@link #stylesheetfile} is empty, return the file as String defined by {@link #stylesheet} value.
*
* If the {@link #stylesheetfile} is defined, return the file as String.
*
* Note: since 2.6, the {@link #stylesheetfile} could be a path from a resource in the project source
* directories (i.e. src/main/java, src/main/resources or src/main/javadoc)
* or from a resource in the Javadoc plugin dependencies.
*
* @param javadocOutputDirectory the output directory
* @return the stylesheet file absolute path as String.
* @see #getResource(List, String)
*/
private Optional getStylesheetFile(final File javadocOutputDirectory) {
if (stylesheetfile == null || stylesheetfile.isEmpty()) {
if ("java".equalsIgnoreCase(stylesheet)) {
// use the default Javadoc tool stylesheet
return Optional.empty();
}
getLog().warn("Parameter 'stylesheet' is no longer evaluated, rather use 'addStylesheets'"
+ " to customize the CSS!");
return Optional.empty();
}
if (new File(stylesheetfile).exists()) {
return Optional.of(new File(stylesheetfile));
}
return getResource(new File(javadocOutputDirectory, DEFAULT_CSS_NAME), stylesheetfile);
}
private void addAddStyleSheets(List arguments) throws MavenReportException {
if (addStylesheets == null) {
return;
}
for (String addStylesheet : addStylesheets) {
Optional styleSheet = getAddStylesheet(getJavadocDirectory(), addStylesheet);
if (styleSheet.isPresent()) {
addArgIfNotEmpty(
arguments,
"--add-stylesheet",
JavadocUtil.quotedPathArgument(styleSheet.get().getAbsolutePath()),
JavaVersion.parse("10"));
}
}
}
private Optional getAddStylesheet(final File javadocOutputDirectory, final String stylesheet)
throws MavenReportException {
if (stylesheet == null || stylesheet.isEmpty()) {
return Optional.empty();
}
File addstylesheetfile = new File(getJavadocDirectory(), stylesheet);
if (addstylesheetfile.exists()) {
Optional stylesheetfile = getStylesheetFile(javadocOutputDirectory);
if (stylesheetfile.isPresent()) {
if (stylesheetfile.get().getName().equals(addstylesheetfile.getName())) {
throw new MavenReportException("additional stylesheet must have a different name "
+ "than stylesheetfile: " + stylesheetfile.get().getName());
}
}
return Optional.of(addstylesheetfile);
}
throw new MavenReportException(
"additional stylesheet file does not exist: " + addstylesheetfile.getAbsolutePath());
}
/**
* Method to get the help file to be used by the Javadoc Tool.
*
* Since 2.6, the {@code helpfile} could be a path from a resource in the project source
* directories (i.e. src/main/java, src/main/resources or src/main/javadoc)
* or from a resource in the Javadoc plugin dependencies.
*
* @param javadocOutputDirectory the output directory.
* @return the help file absolute path as String.
* @see #getResource(File, String)
* @since 2.6
*/
private Optional getHelpFile(final File javadocOutputDirectory) {
if (helpfile == null || helpfile.isEmpty()) {
return Optional.empty();
}
if (new File(helpfile).exists()) {
return Optional.of(new File(helpfile));
}
return getResource(new File(javadocOutputDirectory, "help-doc.html"), helpfile);
}
/**
* Method to get the access level for the classes and members to be shown in the generated javadoc.
* If the specified access level is not public, protected, package or private, the access level
* is set to protected.
*
* @return the access level
*/
private String getAccessLevel() {
String accessLevel;
if ("public".equalsIgnoreCase(show)
|| "protected".equalsIgnoreCase(show)
|| "package".equalsIgnoreCase(show)
|| "private".equalsIgnoreCase(show)) {
accessLevel = "-" + show;
} else {
if (getLog().isErrorEnabled()) {
getLog().error("Unrecognized access level to show '" + show + "'. Defaulting to protected.");
}
accessLevel = "-protected";
}
return accessLevel;
}
/**
* Method to get the path of the bootclass artifacts used in the -bootclasspath option.
*
* @return a string that contains bootclass path, separated by the System pathSeparator string
* (colon (:) on Solaris or semicolon (;) on Windows).
* @throws MavenReportException if any
* @see File#pathSeparator
*/
private String getBootclassPath() throws MavenReportException {
Set bootclasspathArtifacts = collectBootClasspathArtifacts();
List bootclassPath = new ArrayList<>();
for (BootclasspathArtifact aBootclasspathArtifact : bootclasspathArtifacts) {
if ((StringUtils.isNotEmpty(aBootclasspathArtifact.getGroupId()))
&& (StringUtils.isNotEmpty(aBootclasspathArtifact.getArtifactId()))
&& (StringUtils.isNotEmpty(aBootclasspathArtifact.getVersion()))) {
bootclassPath.addAll(getArtifactsAbsolutePath(aBootclasspathArtifact));
}
}
bootclassPath = JavadocUtil.pruneFiles(bootclassPath);
StringBuilder path = new StringBuilder();
path.append(StringUtils.join(bootclassPath.iterator(), File.pathSeparator));
if (bootclasspath != null && !bootclasspath.isEmpty()) {
path.append(JavadocUtil.unifyPathSeparator(bootclasspath));
}
return path.toString();
}
/**
* Method to get the path of the doclet artifacts used in the -docletpath option.
*
* Either docletArtifact or doclectArtifacts can be defined and used, not both, docletArtifact
* takes precedence over doclectArtifacts. docletPath is always appended to any result path
* definition.
*
* @return a string that contains doclet path, separated by the System pathSeparator string
* (colon (:) on Solaris or semicolon (;) on Windows).
* @throws MavenReportException if any
* @see File#pathSeparator
*/
private String getDocletPath() throws MavenReportException {
Set docletArtifacts = collectDocletArtifacts();
List pathParts = new ArrayList<>();
for (DocletArtifact docletArtifact : docletArtifacts) {
if (!isDocletArtifactEmpty(docletArtifact)) {
pathParts.addAll(getArtifactsAbsolutePath(docletArtifact));
}
}
if (!(docletPath == null || docletPath.isEmpty())) {
pathParts.add(JavadocUtil.unifyPathSeparator(docletPath));
}
String path = StringUtils.join(pathParts.iterator(), File.pathSeparator);
if ((path == null || path.isEmpty()) && getLog().isWarnEnabled()) {
getLog().warn("No docletpath option was found. Please review or "
+ " or .");
}
return path;
}
/**
* Verify if a doclet artifact is empty or not
*
* @param aDocletArtifact could be null
* @return true if aDocletArtifact or the groupId/artifactId/version of the doclet artifact is null,
* false otherwise.
*/
private boolean isDocletArtifactEmpty(DocletArtifact aDocletArtifact) {
if (aDocletArtifact == null) {
return true;
}
return StringUtils.isEmpty(aDocletArtifact.getGroupId())
&& StringUtils.isEmpty(aDocletArtifact.getArtifactId())
&& StringUtils.isEmpty(aDocletArtifact.getVersion());
}
/**
* Method to get the path of the taglet artifacts used in the -tagletpath option.
*
* @return a string that contains taglet path, separated by the System pathSeparator string
* (colon (:) on Solaris or semicolon (;) on Windows).
* @throws MavenReportException if any
* @see File#pathSeparator
*/
private String getTagletPath() throws MavenReportException {
Set tArtifacts = collectTagletArtifacts();
Collection pathParts = new ArrayList<>();
for (TagletArtifact tagletArtifact : tArtifacts) {
if ((tagletArtifact != null)
&& (StringUtils.isNotEmpty(tagletArtifact.getGroupId()))
&& (StringUtils.isNotEmpty(tagletArtifact.getArtifactId()))
&& (StringUtils.isNotEmpty(tagletArtifact.getVersion()))) {
pathParts.addAll(getArtifactsAbsolutePath(tagletArtifact));
}
}
Set taglets = collectTaglets();
for (Taglet taglet : taglets) {
if (taglet == null) {
continue;
}
if ((taglet.getTagletArtifact() != null)
&& (StringUtils.isNotEmpty(taglet.getTagletArtifact().getGroupId()))
&& (StringUtils.isNotEmpty(taglet.getTagletArtifact().getArtifactId()))
&& (StringUtils.isNotEmpty(taglet.getTagletArtifact().getVersion()))) {
pathParts.addAll(JavadocUtil.pruneFiles(getArtifactsAbsolutePath(taglet.getTagletArtifact())));
} else if (StringUtils.isNotEmpty(taglet.getTagletpath())) {
for (Path dir : JavadocUtil.pruneDirs(project, Collections.singletonList(taglet.getTagletpath()))) {
pathParts.add(dir.toString());
}
}
}
StringBuilder path = new StringBuilder();
path.append(StringUtils.join(pathParts.iterator(), File.pathSeparator));
if (tagletpath != null && !tagletpath.isEmpty()) {
path.append(JavadocUtil.unifyPathSeparator(tagletpath));
}
return path.toString();
}
private Set collectLinks() throws MavenReportException {
Set links = new LinkedHashSet<>();
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getLinks())) {
links.addAll(options.getLinks());
}
}
}
}
if (isNotEmpty(this.links)) {
links.addAll(this.links);
}
links.addAll(getDependenciesLinks());
return followLinks(links);
}
private Set collectGroups() throws MavenReportException {
Set groups = new LinkedHashSet<>();
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getGroups())) {
groups.addAll(options.getGroups());
}
}
}
}
if (this.groups != null && this.groups.length > 0) {
groups.addAll(Arrays.asList(this.groups));
}
return groups;
}
private Set collectResourcesArtifacts() throws MavenReportException {
Set result = new LinkedHashSet<>();
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getResourcesArtifacts())) {
result.addAll(options.getResourcesArtifacts());
}
}
}
}
if (this.resourcesArtifacts != null && this.resourcesArtifacts.length > 0) {
result.addAll(Arrays.asList(this.resourcesArtifacts));
}
return result;
}
private Set collectBootClasspathArtifacts() throws MavenReportException {
Set result = new LinkedHashSet<>();
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getBootclasspathArtifacts())) {
result.addAll(options.getBootclasspathArtifacts());
}
}
}
}
if (this.bootclasspathArtifacts != null && this.bootclasspathArtifacts.length > 0) {
result.addAll(Arrays.asList(this.bootclasspathArtifacts));
}
return result;
}
private Set collectOfflineLinks() throws MavenReportException {
Set result = new LinkedHashSet<>();
OfflineLink javaApiLink = getDefaultJavadocApiLink();
if (javaApiLink != null) {
result.add(javaApiLink);
}
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getOfflineLinks())) {
result.addAll(options.getOfflineLinks());
}
}
}
}
if (this.offlineLinks != null && this.offlineLinks.length > 0) {
result.addAll(Arrays.asList(this.offlineLinks));
}
return result;
}
private Set collectTags() throws MavenReportException {
Set tags = new LinkedHashSet<>();
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getTags())) {
tags.addAll(options.getTags());
}
}
}
}
if (this.tags != null && this.tags.length > 0) {
tags.addAll(Arrays.asList(this.tags));
}
return tags;
}
private Set collectTagletArtifacts() throws MavenReportException {
Set tArtifacts = new LinkedHashSet<>();
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getTagletArtifacts())) {
tArtifacts.addAll(options.getTagletArtifacts());
}
}
}
}
if (tagletArtifact != null) {
tArtifacts.add(tagletArtifact);
}
if (tagletArtifacts != null && tagletArtifacts.length > 0) {
tArtifacts.addAll(Arrays.asList(tagletArtifacts));
}
return tArtifacts;
}
private Set collectDocletArtifacts() throws MavenReportException {
Set dArtifacts = new LinkedHashSet<>();
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getDocletArtifacts())) {
dArtifacts.addAll(options.getDocletArtifacts());
}
}
}
}
if (docletArtifact != null) {
dArtifacts.add(docletArtifact);
}
if (docletArtifacts != null && docletArtifacts.length > 0) {
dArtifacts.addAll(Arrays.asList(docletArtifacts));
}
return dArtifacts;
}
private Set collectTaglets() throws MavenReportException {
Set result = new LinkedHashSet<>();
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getTaglets())) {
result.addAll(options.getTaglets());
}
}
}
}
if (taglets != null && taglets.length > 0) {
result.addAll(Arrays.asList(taglets));
}
return result;
}
/**
* Return the Javadoc artifact path and its transitive dependencies path from the local repository
*
* @param javadocArtifact not null
* @return a list of locale artifacts absolute path
* @throws MavenReportException if any
*/
private List getArtifactsAbsolutePath(JavadocPathArtifact javadocArtifact) throws MavenReportException {
if ((StringUtils.isEmpty(javadocArtifact.getGroupId()))
&& (StringUtils.isEmpty(javadocArtifact.getArtifactId()))
&& (StringUtils.isEmpty(javadocArtifact.getVersion()))) {
return Collections.emptyList();
}
List path = new ArrayList<>();
try {
Artifact artifact = createAndResolveArtifact(javadocArtifact);
path.add(artifact.getFile().getAbsolutePath());
DependencyFilter filter = new ScopeDependencyFilter(
Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED), Collections.emptySet());
DependencyRequest req = new DependencyRequest(
new CollectRequest(
new org.eclipse.aether.graph.Dependency(RepositoryUtils.toArtifact(artifact), null),
RepositoryUtils.toRepos(project.getRemoteArtifactRepositories())),
filter);
Iterable deps =
repoSystem.resolveDependencies(repoSession, req).getArtifactResults();
for (ArtifactResult a : deps) {
path.add(a.getArtifact().getFile().getAbsolutePath());
}
return path;
} catch (ArtifactResolutionException e) {
throw new MavenReportException("Unable to resolve artifact:" + javadocArtifact, e);
} catch (DependencyResolutionException e) {
throw new MavenReportException("Unable to resolve dependencies for:" + javadocArtifact, e);
}
}
/**
* creates an {@link Artifact} representing the configured {@link JavadocPathArtifact} and resolves it.
*
* @param javadocArtifact the {@link JavadocPathArtifact} to resolve
* @return a resolved {@link Artifact}
* @throws org.eclipse.aether.resolution.ArtifactResolutionException
* @throws ArtifactResolverException issue while resolving artifact
*/
private Artifact createAndResolveArtifact(JavadocPathArtifact javadocArtifact)
throws org.eclipse.aether.resolution.ArtifactResolutionException {
org.eclipse.aether.artifact.Artifact artifact = new DefaultArtifact(
javadocArtifact.getGroupId(),
javadocArtifact.getArtifactId(),
javadocArtifact.getClassifier(),
"jar",
javadocArtifact.getVersion());
ArtifactRequest req = new ArtifactRequest(artifact, project.getRemoteProjectRepositories(), null);
return RepositoryUtils.toArtifact(
repoSystem.resolveArtifact(repoSession, req).getArtifact());
}
/**
* Method that adds/sets the java memory parameters in the command line execution.
*
* @param cmd the command line execution object where the argument will be added
* @param arg the argument parameter name
* @param memory the JVM memory value to be set
* @see JavadocUtil#parseJavadocMemory(String)
*/
private void addMemoryArg(Commandline cmd, String arg, String memory) {
if (memory != null && !memory.isEmpty()) {
try {
cmd.createArg().setValue("-J" + arg + JavadocUtil.parseJavadocMemory(memory));
} catch (IllegalArgumentException e) {
if (getLog().isErrorEnabled()) {
getLog().error("Malformed memory pattern for '" + arg + memory + "'. Ignore this option.");
}
}
}
}
/**
* Method that adds/sets the javadoc proxy parameters in the command line execution.
*
* @param cmd the command line execution object where the argument will be added
*/
private void addProxyArg(Commandline cmd) {
if (settings == null || settings.getProxies().isEmpty()) {
return;
}
Map activeProxies = new HashMap<>();
for (Proxy proxy : settings.getProxies()) {
if (proxy.isActive()) {
String protocol = proxy.getProtocol();
if (!activeProxies.containsKey(protocol)) {
activeProxies.put(protocol, proxy);
}
}
}
if (activeProxies.containsKey("https")) {
Proxy httpsProxy = activeProxies.get("https");
if (StringUtils.isNotEmpty(httpsProxy.getHost())) {
cmd.createArg().setValue("-J-Dhttps.proxyHost=" + httpsProxy.getHost());
cmd.createArg().setValue("-J-Dhttps.proxyPort=" + httpsProxy.getPort());
if (StringUtils.isNotEmpty(httpsProxy.getNonProxyHosts())
&& (!activeProxies.containsKey("http")
|| StringUtils.isEmpty(activeProxies.get("http").getNonProxyHosts()))) {
cmd.createArg()
.setValue("-J-Dhttp.nonProxyHosts=\""
+ httpsProxy.getNonProxyHosts().replace("|", "^|") + "\"");
}
}
}
if (activeProxies.containsKey("http")) {
Proxy httpProxy = activeProxies.get("http");
if (StringUtils.isNotEmpty(httpProxy.getHost())) {
cmd.createArg().setValue("-J-Dhttp.proxyHost=" + httpProxy.getHost());
cmd.createArg().setValue("-J-Dhttp.proxyPort=" + httpProxy.getPort());
if (!activeProxies.containsKey("https")) {
cmd.createArg().setValue("-J-Dhttps.proxyHost=" + httpProxy.getHost());
cmd.createArg().setValue("-J-Dhttps.proxyPort=" + httpProxy.getPort());
}
if (StringUtils.isNotEmpty(httpProxy.getNonProxyHosts())) {
cmd.createArg()
.setValue("-J-Dhttp.nonProxyHosts=\""
+ httpProxy.getNonProxyHosts().replace("|", "^|") + "\"");
}
}
}
// We bravely ignore FTP because no one (probably) uses FTP for Javadoc
}
/**
* Get the path of the Javadoc tool executable depending the user entry or try to find it depending the OS
* or the java.home system property or the JAVA_HOME environment variable.
*
* @return the path of the Javadoc tool
* @throws IOException if not found
*/
private String getJavadocExecutable() throws IOException {
Toolchain tc = getToolchain();
if (tc != null) {
getLog().info("Toolchain in maven-javadoc-plugin: " + tc);
if (javadocExecutable != null) {
getLog().warn("Toolchains are ignored, 'javadocExecutable' parameter is set to " + javadocExecutable);
} else {
javadocExecutable = tc.findTool("javadoc");
}
}
String javadocCommand = "javadoc" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : "");
File javadocExe;
// ----------------------------------------------------------------------
// The javadoc executable is defined by the user
// ----------------------------------------------------------------------
if (javadocExecutable != null && !javadocExecutable.isEmpty()) {
javadocExe = new File(javadocExecutable);
if (javadocExe.isDirectory()) {
javadocExe = new File(javadocExe, javadocCommand);
}
if (SystemUtils.IS_OS_WINDOWS && javadocExe.getName().indexOf('.') < 0) {
javadocExe = new File(javadocExe.getPath() + ".exe");
}
if (!javadocExe.isFile()) {
throw new IOException("The javadoc executable '" + javadocExe
+ "' doesn't exist or is not a file. Verify the parameter.");
}
return javadocExe.getAbsolutePath();
}
// CHECKSTYLE_OFF: LineLength
// ----------------------------------------------------------------------
// Try to find javadocExe from System.getProperty( "java.home" )
// "java.home" is the "Installation directory for Java Runtime
// Environment (JRE)" used to run this code. I.e. it is the parent of
// the directory containing the `java` command used to start this
// application. It does not necessarily have any relation to the
// environment variable JAVA_HOME.
//
// In Java 8 and below the JRE is separate from the JDK. When
// installing the JDK to my-dir/ the javadoc command is installed in
// my-dir/bin/javadoc, the JRE is installed to my-dir/jre, and hence
// the java command is installed to my-dir/jre/bin/java. In this
// configuration "java.home" is mydir/jre/, threfore the relative path
// to the javadoc command is ../bin/javadoc.
//
// In Java 9 and above the JRE is no longer in a subdirectory of the
// JDK, i.e. the JRE and the JDK are merged. In this case the java
// command is installed to my-dir/bin/java along side the javadoc
// command. So the relative path from "java.home" to the javadoc
// command is bin/javadoc.
//
// References
//
// "System Properties" in "The Java Tutorials"
// https://docs.oracle.com/javase/tutorial/essential/environment/sysprop.html
//
// "Changes to the Installed JDK/JRE Image" in "JDK 9 Migration Guide"
// https://docs.oracle.com/javase/9/migrate/toc.htm?xd_co_f=122a7174-9132-4acd-b122-fac02f8c4fef#JSMIG-GUID-D867DCCC-CEB5-4AFA-9D11-9C62B7A3FAB1
//
// "JEP 220: Modular Run-Time Images"
// http://openjdk.java.net/jeps/220
// ----------------------------------------------------------------------
// For IBM's JDK 1.2
// CHECKSTYLE_ON: LineLength
if (SystemUtils.IS_OS_AIX) {
javadocExe =
new File(SystemUtils.getJavaHome() + File.separator + ".." + File.separator + "sh", javadocCommand);
}
// For Apple's JDK 1.6.x (and older?) on Mac OSX
// CHECKSTYLE_OFF: MagicNumber
else if (SystemUtils.IS_OS_MAC_OSX && !JavaVersion.JAVA_SPECIFICATION_VERSION.isAtLeast("1.7"))
// CHECKSTYLE_ON: MagicNumber
{
javadocExe = new File(SystemUtils.getJavaHome() + File.separator + "bin", javadocCommand);
} else if (isJavaVersionAtLeast(org.apache.commons.lang3.JavaVersion.JAVA_9)) {
javadocExe = new File(SystemUtils.getJavaHome() + File.separator + "bin", javadocCommand);
} else {
// Java <= 8
javadocExe = new File(
SystemUtils.getJavaHome() + File.separator + ".." + File.separator + "bin", javadocCommand);
}
// ----------------------------------------------------------------------
// Try to find javadocExe from JAVA_HOME environment variable
// ----------------------------------------------------------------------
if (!javadocExe.exists() || !javadocExe.isFile()) {
Properties env = CommandLineUtils.getSystemEnvVars();
String javaHome = env.getProperty("JAVA_HOME");
if (javaHome == null || javaHome.isEmpty()) {
throw new IOException("The environment variable JAVA_HOME is not correctly set.");
}
if ((!new File(javaHome).getCanonicalFile().exists())
|| (new File(javaHome).getCanonicalFile().isFile())) {
throw new IOException("The environment variable JAVA_HOME=" + javaHome
+ " doesn't exist or is not a valid directory.");
}
javadocExe = new File(javaHome + File.separator + "bin", javadocCommand);
}
if (!javadocExe.getCanonicalFile().exists()
|| !javadocExe.getCanonicalFile().isFile()) {
throw new IOException("The javadoc executable '" + javadocExe
+ "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable.");
}
return javadocExe.getAbsolutePath();
}
/**
* Set a new value for javadocRuntimeVersion
*
* @param jExecutable not null
* @throws MavenReportException if not found
* @see JavadocUtil#getJavadocVersion(File)
*/
private void setFJavadocVersion(File jExecutable) throws MavenReportException {
JavaVersion jVersion;
try {
jVersion = JavadocUtil.getJavadocVersion(jExecutable);
} catch (IOException | CommandLineException | IllegalArgumentException e) {
if (getLog().isWarnEnabled()) {
getLog().warn("Unable to find the javadoc version: " + e.getMessage());
getLog().warn("Using the Java version instead of, i.e. " + JAVA_VERSION);
}
jVersion = JAVA_VERSION;
}
if (javadocVersion != null && !javadocVersion.isEmpty()) {
try {
javadocRuntimeVersion = JavaVersion.parse(javadocVersion);
} catch (NumberFormatException e) {
throw new MavenReportException("Unable to parse javadoc version: " + e.getMessage(), e);
}
if (javadocRuntimeVersion.compareTo(jVersion) != 0 && getLog().isWarnEnabled()) {
getLog().warn("Are you sure about the parameter? It seems to be " + jVersion);
}
} else {
javadocRuntimeVersion = jVersion;
}
}
/**
* Is the Javadoc version at least the requested version.
*
* @param requiredVersion the required version, for example 1.5f
* @return true if the javadoc version is equal or greater than the
* required version
*/
private boolean isJavaDocVersionAtLeast(JavaVersion requiredVersion) {
return JAVA_VERSION.compareTo(requiredVersion) >= 0;
}
/**
* Convenience method to add an argument to the command line
* conditionally based on the given flag.
*
* @param arguments a list of arguments, not null
* @param b the flag which controls if the argument is added or not.
* @param value the argument value to be added.
*/
private void addArgIf(List arguments, boolean b, String value) {
if (b) {
arguments.add(value);
}
}
/**
* Convenience method to add an argument to the command line
* regarding the requested Java version.
*
* @param arguments a list of arguments, not null
* @param b the flag which controls if the argument is added or not.
* @param value the argument value to be added.
* @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
* @see #addArgIf(List, boolean, String)
* @see #isJavaDocVersionAtLeast(JavaVersion)
*/
private void addArgIf(List arguments, boolean b, String value, JavaVersion requiredJavaVersion) {
if (b) {
if (isJavaDocVersionAtLeast(requiredJavaVersion)) {
addArgIf(arguments, true, value);
} else {
if (getLog().isWarnEnabled()) {
getLog().warn(value + " option is not supported on Java version < " + requiredJavaVersion
+ ". Ignore this option.");
}
}
}
}
/**
* Convenience method to add an argument to the command line
* if the the value is not null or empty.
*
* Moreover, the value could be comma separated.
*
* @param arguments a list of arguments, not null
* @param key the argument name.
* @param value the argument value to be added.
* @see #addArgIfNotEmpty(List, String, String, boolean)
*/
private void addArgIfNotEmpty(List arguments, String key, String value) {
addArgIfNotEmpty(arguments, key, value, false);
}
/**
* Convenience method to add an argument to the command line
* if the the value is not null or empty.
*
* Moreover, the value could be comma separated.
*
* @param arguments a list of arguments, not null
* @param key the argument name.
* @param value the argument value to be added.
* @param repeatKey repeat or not the key in the command line
* @param splitValue if true given value will be tokenized by comma
* @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
* @see #addArgIfNotEmpty(List, String, String, boolean, boolean)
* @see #isJavaDocVersionAtLeast(JavaVersion)
*/
private void addArgIfNotEmpty(
List arguments,
String key,
String value,
boolean repeatKey,
boolean splitValue,
JavaVersion requiredJavaVersion) {
if (value != null && !value.isEmpty()) {
if (isJavaDocVersionAtLeast(requiredJavaVersion)) {
addArgIfNotEmpty(arguments, key, value, repeatKey, splitValue);
} else {
if (getLog().isWarnEnabled()) {
getLog().warn(key + " option is not supported on Java version < " + requiredJavaVersion
+ ". Ignore this option.");
}
}
}
}
/**
* Convenience method to add an argument to the command line
* if the the value is not null or empty.
*
* Moreover, the value could be comma separated.
*
* @param arguments a list of arguments, not null
* @param key the argument name.
* @param value the argument value to be added.
* @param repeatKey repeat or not the key in the command line
* @param splitValue if true given value will be tokenized by comma
*/
private void addArgIfNotEmpty(
List arguments, String key, String value, boolean repeatKey, boolean splitValue) {
if (value != null && !value.isEmpty()) {
if (key != null && !key.isEmpty()) {
arguments.add(key);
}
if (splitValue) {
StringTokenizer token = new StringTokenizer(value, ",");
while (token.hasMoreTokens()) {
String current = token.nextToken().trim();
if (current != null && !current.isEmpty()) {
arguments.add(current);
if (token.hasMoreTokens() && repeatKey) {
arguments.add(key);
}
}
}
} else {
arguments.add(value);
}
}
}
/**
* Convenience method to add an argument to the command line
* if the the value is not null or empty.
*
* Moreover, the value could be comma separated.
*
* @param arguments a list of arguments, not null
* @param key the argument name.
* @param value the argument value to be added.
* @param repeatKey repeat or not the key in the command line
*/
private void addArgIfNotEmpty(List arguments, String key, String value, boolean repeatKey) {
addArgIfNotEmpty(arguments, key, value, repeatKey, true);
}
/**
* Convenience method to add an argument to the command line
* regarding the requested Java version.
*
* @param arguments a list of arguments, not null
* @param key the argument name.
* @param value the argument value to be added.
* @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
* @see #addArgIfNotEmpty(List, String, String, JavaVersion, boolean)
*/
private void addArgIfNotEmpty(List arguments, String key, String value, JavaVersion requiredJavaVersion) {
addArgIfNotEmpty(arguments, key, value, requiredJavaVersion, false);
}
/**
* Convenience method to add an argument to the command line
* regarding the requested Java version.
*
* @param arguments a list of arguments, not null
* @param key the argument name.
* @param value the argument value to be added.
* @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
* @param repeatKey repeat or not the key in the command line
* @see #addArgIfNotEmpty(List, String, String)
* @see #isJavaDocVersionAtLeast
*/
private void addArgIfNotEmpty(
List arguments, String key, String value, JavaVersion requiredJavaVersion, boolean repeatKey) {
if (value != null && !value.isEmpty()) {
if (isJavaDocVersionAtLeast(requiredJavaVersion)) {
addArgIfNotEmpty(arguments, key, value, repeatKey);
} else {
if (getLog().isWarnEnabled()) {
getLog().warn(key + " option is not supported on Java version < " + requiredJavaVersion);
}
}
}
}
/**
* Convenience method to process {@code offlineLinks} values as individual -linkoffline
* javadoc options.
*
* If {@code detectOfflineLinks}, try to add javadoc apidocs according Maven conventions for all modules given
* in the project.
*
* @param arguments a list of arguments, not null
* @throws MavenReportException if any
* @see #offlineLinks
* @see #getModulesLinks()
* @see package-list spec
*/
private void addLinkofflineArguments(List arguments, Set offlineLinksList)
throws MavenReportException {
for (OfflineLink offlineLink : offlineLinksList) {
String url = offlineLink.getUrl();
if (url == null || url.isEmpty()) {
continue;
}
url = cleanUrl(url);
String location = offlineLink.getLocation();
if (location == null || location.isEmpty()) {
continue;
}
if (isValidJavadocLink(location, false)) {
addArgIfNotEmpty(
arguments,
"-linkoffline",
JavadocUtil.quotedPathArgument(url) + " " + JavadocUtil.quotedPathArgument(location),
true);
}
}
}
private Set getLinkofflines() throws MavenReportException {
Set offlineLinksList = collectOfflineLinks();
offlineLinksList.addAll(getModulesLinks());
return offlineLinksList;
}
/**
* Convenience method to process {@link #links} values as individual -link javadoc options.
* If {@code detectLinks}, try to add javadoc apidocs according Maven conventions for all dependencies given
* in the project.
*
* According the Javadoc documentation, all defined link should have ${link}/package-list fetchable.
*
* Note: when a link is not fetchable:
*
*
Javadoc 1.4 and less throw an exception
*
Javadoc 1.5 and more display a warning
*
*
* @param arguments a list of arguments, not null
* @throws MavenReportException issue while generating report
* @see #detectLinks
* @see #getDependenciesLinks()
* @see link option
*/
private void addLinkArguments(List arguments) throws MavenReportException {
Set links = collectLinks();
for (String link : links) {
if (link == null || link.isEmpty()) {
continue;
}
if ((settings.isOffline() || offline) && !link.startsWith("file:")) {
continue;
}
while (link.endsWith("/")) {
link = link.substring(0, link.lastIndexOf("/"));
}
addArgIfNotEmpty(arguments, "-link", JavadocUtil.quotedPathArgument(link), true, false);
}
}
/**
* Coppy all resources to the output directory
*
* @param javadocOutputDirectory not null
* @throws MavenReportException if any
* @see #copyDefaultStylesheet(File)
* @see #copyJavadocResources(File)
* @see #copyAdditionalJavadocResources(File)
*/
private void copyAllResources(File javadocOutputDirectory) throws MavenReportException {
// ----------------------------------------------------------------------
// Copy javadoc resources
// ----------------------------------------------------------------------
if (docfilessubdirs) {
/*
* Workaround since -docfilessubdirs doesn't seem to be used correctly by the javadoc tool
* (see other note about -sourcepath). Take care of the -excludedocfilessubdir option.
*/
try {
copyJavadocResources(javadocOutputDirectory);
} catch (IOException e) {
throw new MavenReportException("Unable to copy javadoc resources: " + e.getMessage(), e);
}
}
// ----------------------------------------------------------------------
// Copy additional javadoc resources in artifacts
// ----------------------------------------------------------------------
copyAdditionalJavadocResources(javadocOutputDirectory);
}
/**
* Method that copy all doc-files directories from javadocDirectory of
* the current project or of the projects in the reactor to the outputDirectory.
*
* @param anOutputDirectory the output directory
* @throws java.io.IOException if any
* @see Reference
* Guide, Copies new "doc-files" directory for holding images and examples
* @see #docfilessubdirs
*/
private void copyJavadocResources(File anOutputDirectory) throws IOException {
if (anOutputDirectory == null || !anOutputDirectory.exists()) {
throw new IOException("The outputDirectory " + anOutputDirectory + " doesn't exists.");
}
if (includeDependencySources) {
resolveDependencyBundles();
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
File dir = bundle.getResourcesDirectory();
JavadocOptions options = bundle.getOptions();
if (dir != null && dir.isDirectory()) {
JavadocUtil.copyJavadocResources(
anOutputDirectory, dir, options == null ? null : options.getExcludedDocfilesSubdirs());
}
}
}
}
if (getJavadocDirectory() != null) {
JavadocUtil.copyJavadocResources(anOutputDirectory, getJavadocDirectory(), excludedocfilessubdir);
}
if (isAggregator()) {
for (MavenProject subProject : getAggregatedProjects()) {
if (subProject != project && getJavadocDirectory() != null) {
String javadocDirRelative = PathUtils.toRelative(
project.getBasedir(), getJavadocDirectory().getAbsolutePath());
File javadocDir = new File(subProject.getBasedir(), javadocDirRelative);
JavadocUtil.copyJavadocResources(anOutputDirectory, javadocDir, excludedocfilessubdir);
}
}
}
}
private synchronized void resolveDependencyBundles() throws IOException {
if (dependencyJavadocBundles == null) {
dependencyJavadocBundles =
resourceResolver.resolveDependencyJavadocBundles(getDependencySourceResolverConfig());
if (dependencyJavadocBundles == null) {
dependencyJavadocBundles = new ArrayList<>();
}
}
}
/**
* Method that copy additional Javadoc resources from given artifacts.
*
* @param anOutputDirectory the output directory
* @throws MavenReportException if any
* @see #resourcesArtifacts
*/
private void copyAdditionalJavadocResources(File anOutputDirectory) throws MavenReportException {
Set resourcesArtifacts = collectResourcesArtifacts();
if (isEmpty(resourcesArtifacts)) {
return;
}
UnArchiver unArchiver;
try {
unArchiver = archiverManager.getUnArchiver("jar");
} catch (NoSuchArchiverException e) {
throw new MavenReportException(
"Unable to extract resources artifact. " + "No archiver for 'jar' available.", e);
}
for (ResourcesArtifact item : resourcesArtifacts) {
Artifact artifact;
try {
artifact = createAndResolveArtifact(item);
} catch (ArtifactResolutionException e) {
throw new MavenReportException("Unable to resolve artifact:" + item, e);
}
unArchiver.setSourceFile(artifact.getFile());
unArchiver.setDestDirectory(anOutputDirectory);
// remove the META-INF directory from resource artifact
IncludeExcludeFileSelector[] selectors =
new IncludeExcludeFileSelector[] {new IncludeExcludeFileSelector()};
selectors[0].setExcludes(new String[] {"META-INF/**"});
unArchiver.setFileSelectors(selectors);
getLog().info("Extracting contents of resources artifact: " + artifact.getArtifactId());
try {
unArchiver.extract();
} catch (ArchiverException e) {
throw new MavenReportException(
"Extraction of resources failed. Artifact that failed was: " + artifact.getArtifactId(), e);
}
}
}
/**
* @param sourcePaths could be null
* @return the list of package names for files in the sourcePaths
*/
private List getPackageNames(Map> sourcePaths) {
List returnList = new ArrayList<>();
if (!(sourcepath == null || sourcepath.isEmpty())) {
return returnList;
}
for (Entry> currentPathEntry : sourcePaths.entrySet()) {
for (String currentFile : currentPathEntry.getValue()) {
/*
* Remove the miscellaneous files
* https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
*/
if (currentFile.contains("doc-files")) {
continue;
}
int lastIndexOfSeparator = currentFile.lastIndexOf("/");
if (lastIndexOfSeparator != -1) {
String packagename =
currentFile.substring(0, lastIndexOfSeparator).replace('/', '.');
if (!returnList.contains(packagename)) {
returnList.add(packagename);
}
}
}
}
return returnList;
}
/**
* @param javadocModules not null
* @return a list of exported package names for files in allSourcePaths
* @throws MavenReportException if any
* @see #getFiles
* @see #getSourcePaths()
*/
private Collection getPackageNamesRespectingJavaModules(Collection javadocModules)
throws MavenReportException {
if (!(sourcepath == null || sourcepath.isEmpty())) {
return Collections.emptyList();
}
Set returnList = new LinkedHashSet<>();
for (JavadocModule javadocModule : javadocModules) {
Collection artifactSourcePaths = javadocModule.getSourcePaths();
Set exportedPackages = new HashSet<>();
boolean exportAllPackages;
ResolvePathResult resolvedPath = getResolvePathResult(javadocModule.getArtifactFile());
if (resolvedPath != null && resolvedPath.getModuleNameSource() == ModuleNameSource.MODULEDESCRIPTOR) {
Set exports =
resolvedPath.getModuleDescriptor().exports();
if (exports.isEmpty()) {
continue;
}
for (JavaModuleDescriptor.JavaExports export : exports) {
exportedPackages.add(export.source());
}
exportAllPackages = false;
} else {
exportAllPackages = true;
}
for (Map.Entry> currentPathEntry :
getFiles(artifactSourcePaths).entrySet()) {
for (String currentFile : currentPathEntry.getValue()) {
/*
* Remove the miscellaneous files
* https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
*/
if (currentFile.contains("doc-files")) {
continue;
}
int lastIndexOfSeparator = currentFile.lastIndexOf('/');
if (lastIndexOfSeparator != -1) {
String packagename =
currentFile.substring(0, lastIndexOfSeparator).replace('/', '.');
if (exportAllPackages || exportedPackages.contains(packagename)) {
returnList.add(packagename);
}
}
}
}
}
return returnList;
}
/**
* @param sourcePaths could be null
* @return a list files with unnamed package names for files in the sourcePaths
*/
private List getFilesWithUnnamedPackages(Map> sourcePaths) {
List returnList = new ArrayList<>();
if (!(sourcepath == null || sourcepath.isEmpty())) {
return returnList;
}
for (Entry> currentPathEntry : sourcePaths.entrySet()) {
Path currentSourcePath = currentPathEntry.getKey();
for (String currentFile : currentPathEntry.getValue()) {
/*
* Remove the miscellaneous files
* https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
*/
if (currentFile.contains("doc-files")) {
continue;
}
if (currentFile.indexOf('/') == -1) {
returnList.add(currentSourcePath
.resolve(currentFile)
.toAbsolutePath()
.toString());
}
}
}
return returnList;
}
/**
* Either return only the module descriptor or all sourcefiles per sourcepath
* @param sourcePaths could be null
* @return a list of files
*/
private List getSpecialFiles(Map> sourcePaths) {
if (!(sourcepath == null || sourcepath.isEmpty())) {
return new ArrayList<>();
}
boolean containsModuleDescriptor = false;
for (Collection sourcepathFiles : sourcePaths.values()) {
containsModuleDescriptor = sourcepathFiles.contains("module-info.java");
if (containsModuleDescriptor) {
break;
}
}
if (containsModuleDescriptor) {
return getModuleSourcePathFiles(sourcePaths);
} else {
return getFilesWithUnnamedPackages(sourcePaths);
}
}
private List getModuleSourcePathFiles(Map> sourcePaths) {
List returnList = new ArrayList<>();
for (Entry> currentPathEntry : sourcePaths.entrySet()) {
Path currentSourcePath = currentPathEntry.getKey();
if (currentPathEntry.getValue().contains("module-info.java")) {
returnList.add(currentSourcePath
.resolve("module-info.java")
.toAbsolutePath()
.toString());
} else {
for (String currentFile : currentPathEntry.getValue()) {
/*
* Remove the miscellaneous files
* https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
*/
if (currentFile.contains("doc-files")) {
continue;
}
returnList.add(currentSourcePath
.resolve(currentFile)
.toAbsolutePath()
.toString());
}
}
}
return returnList;
}
/**
* Generate an options file for all options and arguments and add the @options in the
* command line.
*
* @param cmd not null
* @param arguments not null
* @param javadocOutputDirectory not null
* @throws MavenReportException if any
* @see
* Reference Guide, Command line argument files
* @see #OPTIONS_FILE_NAME
*/
private void addCommandLineOptions(Commandline cmd, List arguments, File javadocOutputDirectory)
throws MavenReportException {
File optionsFile = new File(javadocOutputDirectory, OPTIONS_FILE_NAME);
StringBuilder options = new StringBuilder();
options.append(StringUtils.join(arguments.iterator(), SystemUtils.LINE_SEPARATOR));
Charset outputFileEncoding;
if (JAVA_VERSION.isAtLeast("9") && JAVA_VERSION.isBefore("12")) {
outputFileEncoding = StandardCharsets.UTF_8;
} else {
outputFileEncoding = Charset.defaultCharset();
}
try {
Files.write(optionsFile.toPath(), Collections.singleton(options), outputFileEncoding);
} catch (IOException e) {
throw new MavenReportException(
"Unable to write '" + optionsFile.getName() + "' temporary file for command execution", e);
}
cmd.createArg().setValue("@" + OPTIONS_FILE_NAME);
}
/**
* Generate a file called argfile (or files, depending the JDK) to hold files and add
* the @argfile (or @file, depending the JDK) in the command line.
*
* @param cmd not null
* @param javadocOutputDirectory not null
* @param files not null
* @throws MavenReportException if any
* @see
* Reference Guide, Command line argument files
*
* @see
* What s New in Javadoc 1.4
*
* @see #isJavaDocVersionAtLeast(JavaVersion)
* @see #ARGFILE_FILE_NAME
* @see #FILES_FILE_NAME
*/
private void addCommandLineArgFile(Commandline cmd, File javadocOutputDirectory, List files)
throws MavenReportException {
File argfileFile;
if (JAVA_VERSION.compareTo(SINCE_JAVADOC_1_4) >= 0) {
argfileFile = new File(javadocOutputDirectory, ARGFILE_FILE_NAME);
cmd.createArg().setValue("@" + ARGFILE_FILE_NAME);
} else {
argfileFile = new File(javadocOutputDirectory, FILES_FILE_NAME);
cmd.createArg().setValue("@" + FILES_FILE_NAME);
}
List quotedFiles = new ArrayList<>(files.size());
for (String file : files) {
quotedFiles.add(JavadocUtil.quotedPathArgument(file));
}
Charset cs;
if (JavaVersion.JAVA_SPECIFICATION_VERSION.isAtLeast("9")
&& JavaVersion.JAVA_SPECIFICATION_VERSION.isBefore("12")) {
cs = StandardCharsets.UTF_8;
} else {
cs = Charset.defaultCharset();
}
try {
Files.write(argfileFile.toPath(), quotedFiles, cs);
} catch (IOException e) {
throw new MavenReportException(
"Unable to write '" + argfileFile.getName() + "' temporary file for command execution", e);
}
}
/**
* Generate a file called packages to hold all package names and add the @packages in
* the command line.
*
* @param cmd not null
* @param javadocOutputDirectory not null
* @param packageNames not null
* @throws MavenReportException if any
* @see
* Reference Guide, Command line argument files
* @see #PACKAGES_FILE_NAME
*/
private void addCommandLinePackages(Commandline cmd, File javadocOutputDirectory, Collection packageNames)
throws MavenReportException {
File packagesFile = new File(javadocOutputDirectory, PACKAGES_FILE_NAME);
try {
FileUtils.fileWrite(
packagesFile.getAbsolutePath(),
null /* platform encoding */,
StringUtils.join(packageNames.iterator(), SystemUtils.LINE_SEPARATOR));
} catch (IOException e) {
throw new MavenReportException(
"Unable to write '" + packagesFile.getName() + "' temporary file for command execution", e);
}
cmd.createArg().setValue("@" + PACKAGES_FILE_NAME);
}
/**
* Checks for the validity of the Javadoc options used by the user.
*
* @throws MavenReportException if error
*/
private void validateJavadocOptions() throws MavenReportException {
// encoding
if (StringUtils.isNotEmpty(getEncoding()) && !JavadocUtil.validateEncoding(getEncoding())) {
throw new MavenReportException("Unsupported option '" + getEncoding() + "'");
}
// locale
if (this.locale != null && !this.locale.isEmpty()) {
StringTokenizer tokenizer = new StringTokenizer(this.locale, "_");
final int maxTokens = 3;
if (tokenizer.countTokens() > maxTokens) {
throw new MavenReportException(
"Unsupported option '" + this.locale + "', should be language_country_variant.");
}
Locale localeObject = null;
if (tokenizer.hasMoreTokens()) {
String language = tokenizer.nextToken().toLowerCase(Locale.ENGLISH);
if (!Arrays.asList(Locale.getISOLanguages()).contains(language)) {
throw new MavenReportException(
"Unsupported language '" + language + "' in option '" + this.locale + "'");
}
localeObject = new Locale(language);
if (tokenizer.hasMoreTokens()) {
String country = tokenizer.nextToken().toUpperCase(Locale.ENGLISH);
if (!Arrays.asList(Locale.getISOCountries()).contains(country)) {
throw new MavenReportException(
"Unsupported country '" + country + "' in option '" + this.locale + "'");
}
localeObject = new Locale(language, country);
if (tokenizer.hasMoreTokens()) {
String variant = tokenizer.nextToken();
localeObject = new Locale(language, country, variant);
}
}
}
if (localeObject == null) {
throw new MavenReportException(
"Unsupported option '" + this.locale + "', should be language_country_variant.");
}
this.locale = localeObject.toString();
final List availableLocalesList = Arrays.asList(Locale.getAvailableLocales());
if (StringUtils.isNotEmpty(localeObject.getVariant()) && !availableLocalesList.contains(localeObject)) {
StringBuilder sb = new StringBuilder();
sb.append("Unsupported option with variant '").append(this.locale);
sb.append("'");
localeObject = new Locale(localeObject.getLanguage(), localeObject.getCountry());
this.locale = localeObject.toString();
sb.append(", trying to use without variant, i.e. '")
.append(this.locale)
.append("'");
if (getLog().isWarnEnabled()) {
getLog().warn(sb.toString());
}
}
if (!availableLocalesList.contains(localeObject)) {
throw new MavenReportException("Unsupported option '" + this.locale + "'");
}
}
}
/**
* Checks for the validity of the Standard Doclet options.
*
* For example, throw an exception if <nohelp/> and <helpfile/> options are used together.
*
* @throws MavenReportException if error or conflict found
*/
private void validateStandardDocletOptions() throws MavenReportException {
// docencoding
if (StringUtils.isNotEmpty(getDocencoding()) && !JavadocUtil.validateEncoding(getDocencoding())) {
throw new MavenReportException("Unsupported option '" + getDocencoding() + "'");
}
// charset
if (StringUtils.isNotEmpty(getCharset()) && !JavadocUtil.validateEncoding(getCharset())) {
throw new MavenReportException("Unsupported option '" + getCharset() + "'");
}
// helpfile
if ((helpfile != null && !helpfile.isEmpty()) && nohelp) {
throw new MavenReportException("Option conflicts with ");
}
// overview
if (getOverview() != null && getOverview().exists() && nooverview) {
throw new MavenReportException("Option conflicts with ");
}
// index
if (splitindex && noindex) {
throw new MavenReportException("Option conflicts with ");
}
// stylesheet
if ((stylesheet != null && !stylesheet.isEmpty())
&& !(stylesheet.equalsIgnoreCase("maven") || stylesheet.equalsIgnoreCase("java"))) {
throw new MavenReportException("Option supports only \"maven\" or \"java\" value.");
}
}
/**
* Add Standard Javadoc Options.
*
* The package documentation details the
* Standard Javadoc Options wrapped by this Plugin.
*
* @param javadocOutputDirectory not null
* @param arguments not null
* @param allSourcePaths not null
* @throws MavenReportException if any
* @see https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadocoptions
*/
private void addJavadocOptions(
File javadocOutputDirectory,
List arguments,
Collection allSourcePaths,
Set offlineLinks)
throws MavenReportException {
Collection sourcePaths = allSourcePaths.stream()
.flatMap(e -> e.getSourcePaths().stream())
.collect(Collectors.toList());
validateJavadocOptions();
// see com.sun.tools.javadoc.Start#parseAndExecute(String argv[])
addArgIfNotEmpty(arguments, "-locale", JavadocUtil.quotedArgument(this.locale));
// all options in alphabetical order
if (old && isJavaDocVersionAtLeast(SINCE_JAVADOC_1_4)) {
if (getLog().isWarnEnabled()) {
getLog().warn("Javadoc 1.4+ doesn't support the -1.1 switch anymore. Ignore this option.");
}
} else {
addArgIf(arguments, old, "-1.1");
}
addArgIfNotEmpty(arguments, "-bootclasspath", JavadocUtil.quotedPathArgument(getBootclassPath()));
if (isJavaDocVersionAtLeast(SINCE_JAVADOC_1_5)) {
addArgIf(arguments, breakiterator, "-breakiterator", SINCE_JAVADOC_1_5);
}
List aggregatedProjects = reactorProjects; // getAggregatedProjects();
Map reactorKeys = new HashMap<>(aggregatedProjects.size());
for (MavenProject reactorProject : aggregatedProjects) {
reactorKeys.put(
ArtifactUtils.versionlessKey(reactorProject.getGroupId(), reactorProject.getArtifactId()),
reactorProject);
}
Map allModuleDescriptors = new HashMap<>();
// do not support the module path in legacy mode
boolean supportModulePath = !legacyMode;
if (supportModulePath) {
supportModulePath &= javadocRuntimeVersion.isAtLeast("9");
if (release != null) {
supportModulePath &= JavaVersion.parse(release).isAtLeast("9");
} else if (source != null) {
supportModulePath &= JavaVersion.parse(source).isAtLeast("9");
}
}
if (supportModulePath) {
for (JavadocModule entry : allSourcePaths) {
if (entry.getModuleNameSource() == null || entry.getModuleNameSource() == ModuleNameSource.FILENAME) {
Path moduleDescriptor = findMainDescriptor(entry.getSourcePaths());
if (moduleDescriptor != null) {
try {
allModuleDescriptors.put(
entry.getGa(),
locationManager
.parseModuleDescriptor(moduleDescriptor)
.getModuleDescriptor());
} catch (IOException e) {
throw new MavenReportException(e.getMessage(), e);
}
}
} else {
allModuleDescriptors.put(entry.getGa(), entry.getModuleDescriptor());
}
}
}
Collection additionalModules = new ArrayList<>();
ResolvePathResult mainResolvePathResult = null;
Map> patchModules = new HashMap<>();
Path moduleSourceDir = null;
if (supportModulePath && !allModuleDescriptors.isEmpty()) {
Collection unnamedProjects = new ArrayList<>();
for (JavadocModule javadocModule : allSourcePaths) {
MavenProject aggregatedProject = reactorKeys.get(javadocModule.getGa());
if (aggregatedProject != null && !"pom".equals(aggregatedProject.getPackaging())) {
ResolvePathResult result = null;
// Prefer jar over outputDirectory, since it may may contain an automatic module name
File artifactFile = getClassesFile(aggregatedProject);
if (artifactFile != null) {
ResolvePathRequest request = ResolvePathRequest.ofFile(artifactFile);
try {
result = locationManager.resolvePath(request);
} catch (RuntimeException e) {
// most likely an invalid module name based on filename
if (!"java.lang.module.FindException"
.equals(e.getClass().getName())) {
throw e;
}
} catch (IOException e) {
throw new MavenReportException(e.getMessage(), e);
}
} else {
Path moduleDescriptor = findMainDescriptor(javadocModule.getSourcePaths());
if (moduleDescriptor != null) {
try {
result = locationManager.parseModuleDescriptor(moduleDescriptor);
} catch (IOException e) {
throw new MavenReportException(e.getMessage(), e);
}
}
}
if (result != null && result.getModuleDescriptor() != null) {
moduleSourceDir = javadocOutputDirectory.toPath().resolve("src");
try {
moduleSourceDir = Files.createDirectories(moduleSourceDir);
additionalModules.add(result.getModuleDescriptor().name());
patchModules.put(result.getModuleDescriptor().name(), javadocModule.getSourcePaths());
Path modulePath = moduleSourceDir.resolve(
result.getModuleDescriptor().name());
if (!Files.isDirectory(modulePath)) {
Files.createDirectory(modulePath);
}
} catch (IOException e) {
throw new MavenReportException(e.getMessage(), e);
}
} else {
unnamedProjects.add(javadocModule.getGa());
}
if (aggregatedProject.equals(getProject())) {
mainResolvePathResult = result;
}
} else {
// todo
getLog().error("no reactor project: " + javadocModule.getGa());
}
}
if (!unnamedProjects.isEmpty()) {
getLog().error("Creating an aggregated report for both named and unnamed modules is not possible.");
getLog().error("Ensure that every module has a module descriptor or is a jar with a MANIFEST.MF "
+ "containing an Automatic-Module-Name.");
getLog().error("Fix the following projects:");
for (String unnamedProject : unnamedProjects) {
getLog().error(" - " + unnamedProject);
}
throw new MavenReportException("Aggregator report contains named and unnamed modules");
}
if (mainResolvePathResult != null
&& ModuleNameSource.MANIFEST.equals(mainResolvePathResult.getModuleNameSource())) {
arguments.add("--add-modules");
arguments.add("ALL-MODULE-PATH");
}
}
// MJAVADOC-506
boolean moduleDescriptorSource = false;
for (Path sourcepath : sourcePaths) {
if (Files.isRegularFile(sourcepath.resolve("module-info.java"))) {
moduleDescriptorSource = true;
break;
}
}
final ModuleNameSource mainModuleNameSource;
if (mainResolvePathResult != null) {
mainModuleNameSource = mainResolvePathResult.getModuleNameSource();
} else {
mainModuleNameSource = null;
}
if (supportModulePath
&& (isAggregator()
|| ModuleNameSource.MODULEDESCRIPTOR.equals(mainModuleNameSource)
|| ModuleNameSource.MANIFEST.equals(mainModuleNameSource))) {
List pathElements = new ArrayList<>(getPathElements());
File artifactFile = getClassesFile(project);
if (artifactFile != null) {
pathElements.add(0, artifactFile);
}
ResolvePathsRequest request = ResolvePathsRequest.ofFiles(pathElements);
String mainModuleName = null;
if (mainResolvePathResult != null) {
request.setModuleDescriptor(mainResolvePathResult.getModuleDescriptor());
mainModuleName = mainResolvePathResult.getModuleDescriptor().name();
}
request.setAdditionalModules(additionalModules);
request.setIncludeStatic(isAggregator());
try {
ResolvePathsResult result = locationManager.resolvePaths(request);
Set modulePathElements =
new HashSet<>(result.getModulepathElements().keySet());
Collection classPathElements =
new ArrayList<>(result.getClasspathElements().size());
for (File file : result.getClasspathElements()) {
if (file.isDirectory() && new File(file, "module-info.class").exists()) {
modulePathElements.add(file);
} else if (ModuleNameSource.MANIFEST.equals(mainModuleNameSource)) {
ModuleNameSource depModuleNameSource = locationManager
.resolvePath(ResolvePathRequest.ofFile(file))
.getModuleNameSource();
if (ModuleNameSource.MODULEDESCRIPTOR.equals(depModuleNameSource)) {
modulePathElements.add(file);
} else {
patchModules.get(mainModuleName).add(file.toPath());
}
} else {
classPathElements.add(file);
}
}
/* MJAVADOC-620: also add all JARs where module-name-guessing leads to a FindException: */
for (Entry pathExceptionEntry :
result.getPathExceptions().entrySet()) {
Exception exception = pathExceptionEntry.getValue();
// For Java < 9 compatibility, reference FindException by name:
if ("java.lang.module.FindException"
.equals(exception.getClass().getName())) {
File jarPath = pathExceptionEntry.getKey();
classPathElements.add(jarPath);
}
}
String classpath = StringUtils.join(classPathElements.iterator(), File.pathSeparator);
addArgIfNotEmpty(arguments, "--class-path", JavadocUtil.quotedPathArgument(classpath), false, false);
String modulepath = StringUtils.join(modulePathElements.iterator(), File.pathSeparator);
addArgIfNotEmpty(arguments, "--module-path", JavadocUtil.quotedPathArgument(modulepath), false, false);
} catch (IOException e) {
throw new MavenReportException(e.getMessage(), e);
}
} else if (supportModulePath && moduleDescriptorSource && !isTest()) {
String modulepath = StringUtils.join(getPathElements().iterator(), File.pathSeparator);
addArgIfNotEmpty(arguments, "--module-path", JavadocUtil.quotedPathArgument(modulepath), false, false);
} else {
String classpath = StringUtils.join(getPathElements().iterator(), File.pathSeparator);
addArgIfNotEmpty(arguments, "-classpath", JavadocUtil.quotedPathArgument(classpath), false, false);
}
for (Entry> entry : patchModules.entrySet()) {
if (!entry.getValue().isEmpty()) {
addArgIfNotEmpty(
arguments,
"--patch-module",
entry.getKey() + '=' + JavadocUtil.quotedPathArgument(getSourcePath(entry.getValue())),
false,
false);
}
}
if (doclet != null && !doclet.isEmpty()) {
addArgIfNotEmpty(arguments, "-doclet", JavadocUtil.quotedArgument(doclet));
addArgIfNotEmpty(arguments, "-docletpath", JavadocUtil.quotedPathArgument(getDocletPath()));
}
if (encoding == null || encoding.isEmpty()) {
getLog().warn("Source files encoding has not been set, using platform encoding "
+ ReaderFactory.FILE_ENCODING + ", i.e. build is platform dependent!");
}
addArgIfNotEmpty(arguments, "-encoding", JavadocUtil.quotedArgument(getEncoding()));
addArgIfNotEmpty(
arguments, "-extdirs", JavadocUtil.quotedPathArgument(JavadocUtil.unifyPathSeparator(extdirs)));
if ((getOverview() != null) && (getOverview().exists())) {
addArgIfNotEmpty(
arguments,
"-overview",
JavadocUtil.quotedPathArgument(getOverview().getAbsolutePath()));
}
arguments.add(getAccessLevel());
if (isJavaDocVersionAtLeast(SINCE_JAVADOC_1_5)) {
addArgIf(arguments, quiet, "-quiet", SINCE_JAVADOC_1_5);
}
if (release != null) {
arguments.add("--release");
arguments.add(release);
} else {
addArgIfNotEmpty(arguments, "-source", JavadocUtil.quotedArgument(source), SINCE_JAVADOC_1_4);
}
if ((sourcepath == null || sourcepath.isEmpty()) && (subpackages != null && !subpackages.isEmpty())) {
sourcepath = StringUtils.join(sourcePaths.iterator(), File.pathSeparator);
}
if (moduleSourceDir == null) {
addArgIfNotEmpty(
arguments, "-sourcepath", JavadocUtil.quotedPathArgument(getSourcePath(sourcePaths)), false, false);
} else if (mainResolvePathResult == null
|| ModuleNameSource.MODULEDESCRIPTOR.equals(mainResolvePathResult.getModuleNameSource())) {
addArgIfNotEmpty(
arguments, "--module-source-path", JavadocUtil.quotedPathArgument(moduleSourceDir.toString()));
}
if ((sourcepath != null && !sourcepath.isEmpty()) && isJavaDocVersionAtLeast(SINCE_JAVADOC_1_5)) {
addArgIfNotEmpty(arguments, "-subpackages", subpackages, SINCE_JAVADOC_1_5);
}
// [MJAVADOC-497] must be after sourcepath is recalculated, since getExcludedPackages() depends on it
addArgIfNotEmpty(arguments, "-exclude", getExcludedPackages(sourcePaths), SINCE_JAVADOC_1_4);
addArgIf(arguments, verbose, "-verbose");
if (additionalOptions != null && additionalOptions.length > 0) {
for (String additionalOption : additionalOptions) {
arguments.add(additionalOption.replaceAll("(? resolvePathRequest = ResolvePathRequest.ofFile(artifactFile);
try {
resolvePathResult = locationManager.resolvePath(resolvePathRequest);
// happens when artifactFile is a directory without module descriptor
if (resolvePathResult.getModuleDescriptor() == null) {
return null;
}
} catch (IOException | RuntimeException /* e.g java.lang.module.FindException */ e) {
if (getLog().isDebugEnabled()) {
Throwable cause = e;
while (cause.getCause() != null) {
cause = cause.getCause();
}
getLog().debug("resolve path for: " + artifactFile + " cause error: " + cause);
}
}
return resolvePathResult;
}
private Path findMainDescriptor(Collection roots) throws MavenReportException {
for (Map.Entry> entry : getFiles(roots).entrySet()) {
if (entry.getValue().contains("module-info.java")) {
return entry.getKey().resolve("module-info.java");
}
}
return null;
}
/**
* Add Standard Doclet Options.
*
* The package documentation details the
* Standard Doclet Options wrapped by this Plugin.
*
* @param javadocOutputDirectory not null
* @param arguments not null
* @throws MavenReportException if any
* @see
* https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#standard
*/
private void addStandardDocletOptions(
File javadocOutputDirectory, List arguments, Set offlineLinks)
throws MavenReportException {
validateStandardDocletOptions();
// all options in alphabetical order
addArgIf(arguments, author, "-author");
addArgIfNotEmpty(arguments, "-bottom", JavadocUtil.quotedArgument(getBottomText()), false, false);
if (!isJavaDocVersionAtLeast(SINCE_JAVADOC_1_5)) {
addArgIf(arguments, breakiterator, "-breakiterator", SINCE_JAVADOC_1_4);
}
addArgIfNotEmpty(arguments, "-charset", JavadocUtil.quotedArgument(getCharset()));
addArgIfNotEmpty(arguments, "-d", JavadocUtil.quotedPathArgument(javadocOutputDirectory.toString()));
addArgIfNotEmpty(arguments, "-docencoding", JavadocUtil.quotedArgument(getDocencoding()));
addArgIf(arguments, docfilessubdirs, "-docfilessubdirs", SINCE_JAVADOC_1_4);
addArgIf(arguments, (doclint != null && !doclint.isEmpty()), "-Xdoclint:" + getDoclint(), SINCE_JAVADOC_1_8);
addArgIfNotEmpty(arguments, "-doctitle", JavadocUtil.quotedArgument(getDoctitle()), false, false);
if (docfilessubdirs) {
addArgIfNotEmpty(
arguments,
"-excludedocfilessubdir",
JavadocUtil.quotedPathArgument(excludedocfilessubdir),
SINCE_JAVADOC_1_4);
}
addArgIfNotEmpty(arguments, "-footer", JavadocUtil.quotedArgument(footer), false, false);
addGroups(arguments);
addArgIfNotEmpty(arguments, "-header", JavadocUtil.quotedArgument(header), false, false);
Optional helpFile = getHelpFile(javadocOutputDirectory);
if (helpFile.isPresent()) {
addArgIfNotEmpty(
arguments,
"-helpfile",
JavadocUtil.quotedPathArgument(helpFile.get().getAbsolutePath()));
}
addArgIf(arguments, keywords, "-keywords", SINCE_JAVADOC_1_4_2);
addLinkArguments(arguments);
addLinkofflineArguments(arguments, offlineLinks);
addArgIf(arguments, linksource, "-linksource", SINCE_JAVADOC_1_4);
if (sourcetab > 0) {
if (javadocRuntimeVersion == SINCE_JAVADOC_1_4_2) {
addArgIfNotEmpty(arguments, "-linksourcetab", String.valueOf(sourcetab));
}
addArgIfNotEmpty(arguments, "-sourcetab", String.valueOf(sourcetab), SINCE_JAVADOC_1_5);
}
addArgIf(arguments, nocomment, "-nocomment", SINCE_JAVADOC_1_4);
addArgIf(arguments, nodeprecated, "-nodeprecated");
addArgIf(arguments, nodeprecatedlist, "-nodeprecatedlist");
addArgIf(arguments, nohelp, "-nohelp");
addArgIf(arguments, noindex, "-noindex");
addArgIf(arguments, nonavbar, "-nonavbar");
addArgIf(arguments, nooverview, "-nooverview");
addArgIfNotEmpty(arguments, "-noqualifier", JavadocUtil.quotedArgument(noqualifier), SINCE_JAVADOC_1_4);
addArgIf(arguments, nosince, "-nosince");
if (!notimestamp
&& MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).isPresent()) {
// Override the notimestamp option if a Reproducible Build is requested.
notimestamp = true;
}
addArgIf(arguments, notimestamp, "-notimestamp", SINCE_JAVADOC_1_5);
addArgIf(arguments, notree, "-notree");
addArgIfNotEmpty(arguments, "-packagesheader", JavadocUtil.quotedArgument(packagesheader), SINCE_JAVADOC_1_4_2);
if (!isJavaDocVersionAtLeast(SINCE_JAVADOC_1_5)) // Sun bug: 4714350
{
addArgIf(arguments, quiet, "-quiet", SINCE_JAVADOC_1_4);
}
addArgIf(arguments, serialwarn, "-serialwarn");
addArgIf(arguments, splitindex, "-splitindex");
Optional stylesheetfile = getStylesheetFile(javadocOutputDirectory);
if (stylesheetfile.isPresent()) {
addArgIfNotEmpty(
arguments,
"-stylesheetfile",
JavadocUtil.quotedPathArgument(stylesheetfile.get().getAbsolutePath()));
}
addAddStyleSheets(arguments);
if ((sourcepath != null && !sourcepath.isEmpty()) && !isJavaDocVersionAtLeast(SINCE_JAVADOC_1_5)) {
addArgIfNotEmpty(arguments, "-subpackages", subpackages, SINCE_JAVADOC_1_4);
}
addArgIfNotEmpty(arguments, "-taglet", JavadocUtil.quotedArgument(taglet), SINCE_JAVADOC_1_4);
addTaglets(arguments);
addTagletsFromTagletArtifacts(arguments);
addArgIfNotEmpty(arguments, "-tagletpath", JavadocUtil.quotedPathArgument(getTagletPath()), SINCE_JAVADOC_1_4);
addTags(arguments);
addArgIfNotEmpty(arguments, "-top", JavadocUtil.quotedArgument(top), false, false, SINCE_JAVADOC_1_6);
addArgIf(arguments, use, "-use");
addArgIf(arguments, version, "-version");
addArgIfNotEmpty(arguments, "-windowtitle", JavadocUtil.quotedArgument(getWindowtitle()), false, false);
}
/**
* Add groups parameter to arguments.
*
* @param arguments not null
* @throws MavenReportException
*/
private void addGroups(List arguments) throws MavenReportException {
Set groups = collectGroups();
if (isEmpty(groups)) {
return;
}
for (Group group : groups) {
if (group == null || StringUtils.isEmpty(group.getTitle()) || StringUtils.isEmpty(group.getPackages())) {
if (getLog().isWarnEnabled()) {
getLog().warn("A group option is empty. Ignore this option.");
}
} else {
String groupTitle = StringUtils.replace(group.getTitle(), ",", ",");
addArgIfNotEmpty(
arguments,
"-group",
JavadocUtil.quotedArgument(groupTitle) + " " + JavadocUtil.quotedArgument(group.getPackages()),
true);
}
}
}
/**
* Add tags parameter to arguments.
*
* @param arguments not null
* @throws MavenReportException
*/
private void addTags(List arguments) throws MavenReportException {
final String lineSeparator;
if (javadocRuntimeVersion.isBefore("9")) {
lineSeparator = " ";
} else {
lineSeparator = " \\\\" + SystemUtils.LINE_SEPARATOR;
}
for (Tag tag : collectTags()) {
if (StringUtils.isEmpty(tag.getName())) {
if (getLog().isWarnEnabled()) {
getLog().warn("A tag name is empty. Ignore this option.");
}
} else {
String value = "\"" + tag.getName();
if (StringUtils.isNotEmpty(tag.getPlacement())) {
value += ":" + tag.getPlacement().replaceAll("\\R", lineSeparator);
if (StringUtils.isNotEmpty(tag.getHead())) {
value += ":" + tag.getHead().replaceAll("\\R", lineSeparator);
}
}
value += "\"";
addArgIfNotEmpty(arguments, "-tag", value, SINCE_JAVADOC_1_4);
}
}
}
/**
* Add taglets parameter to arguments.
*
* @param arguments not null
*/
private void addTaglets(List arguments) {
if (taglets == null) {
return;
}
for (Taglet taglet1 : taglets) {
if ((taglet1 == null) || (StringUtils.isEmpty(taglet1.getTagletClass()))) {
if (getLog().isWarnEnabled()) {
getLog().warn("A taglet option is empty. Ignore this option.");
}
} else {
addArgIfNotEmpty(
arguments, "-taglet", JavadocUtil.quotedArgument(taglet1.getTagletClass()), SINCE_JAVADOC_1_4);
}
}
}
/**
* Auto-detect taglets class name from tagletArtifacts and add them to arguments.
*
* @param arguments not null
* @throws MavenReportException if any
* @see JavadocUtil#getTagletClassNames(File)
*/
private void addTagletsFromTagletArtifacts(List arguments) throws MavenReportException {
Set tArtifacts = new LinkedHashSet<>();
if (tagletArtifacts != null && tagletArtifacts.length > 0) {
tArtifacts.addAll(Arrays.asList(tagletArtifacts));
}
if (includeDependencySources) {
try {
resolveDependencyBundles();
} catch (IOException e) {
throw new MavenReportException(
"Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
}
if (isNotEmpty(dependencyJavadocBundles)) {
for (JavadocBundle bundle : dependencyJavadocBundles) {
JavadocOptions options = bundle.getOptions();
if (options != null && isNotEmpty(options.getTagletArtifacts())) {
tArtifacts.addAll(options.getTagletArtifacts());
}
}
}
}
if (isEmpty(tArtifacts)) {
return;
}
List tagletsPath = new ArrayList<>();
for (TagletArtifact aTagletArtifact : tArtifacts) {
if ((StringUtils.isNotEmpty(aTagletArtifact.getGroupId()))
&& (StringUtils.isNotEmpty(aTagletArtifact.getArtifactId()))
&& (StringUtils.isNotEmpty(aTagletArtifact.getVersion()))) {
Artifact artifact;
try {
artifact = createAndResolveArtifact(aTagletArtifact);
} catch (ArtifactResolutionException e) {
throw new MavenReportException("Unable to resolve artifact:" + aTagletArtifact, e);
}
tagletsPath.add(artifact.getFile().getAbsolutePath());
}
}
tagletsPath = JavadocUtil.pruneFiles(tagletsPath);
for (String tagletJar : tagletsPath) {
if (!tagletJar.toLowerCase(Locale.ENGLISH).endsWith(".jar")) {
continue;
}
List tagletClasses;
try {
tagletClasses = JavadocUtil.getTagletClassNames(new File(tagletJar));
} catch (IOException e) {
if (getLog().isWarnEnabled()) {
getLog().warn("Unable to auto-detect Taglet class names from '" + tagletJar
+ "'. Try to specify them with .");
}
if (getLog().isDebugEnabled()) {
getLog().debug("IOException: " + e.getMessage(), e);
}
continue;
} catch (ClassNotFoundException e) {
if (getLog().isWarnEnabled()) {
getLog().warn("Unable to auto-detect Taglet class names from '" + tagletJar
+ "'. Try to specify them with .");
}
if (getLog().isDebugEnabled()) {
getLog().debug("ClassNotFoundException: " + e.getMessage(), e);
}
continue;
} catch (NoClassDefFoundError e) {
if (getLog().isWarnEnabled()) {
getLog().warn("Unable to auto-detect Taglet class names from '" + tagletJar
+ "'. Try to specify them with .");
}
if (getLog().isDebugEnabled()) {
getLog().debug("NoClassDefFoundError: " + e.getMessage(), e);
}
continue;
}
if (tagletClasses != null && !tagletClasses.isEmpty()) {
for (String tagletClass : tagletClasses) {
addArgIfNotEmpty(arguments, "-taglet", JavadocUtil.quotedArgument(tagletClass), SINCE_JAVADOC_1_4);
}
}
}
}
/**
* Execute the Javadoc command line
*
* @param cmd not null
* @param javadocOutputDirectory not null
* @throws MavenReportException if any errors occur
*/
private void executeJavadocCommandLine(Commandline cmd, File javadocOutputDirectory) throws MavenReportException {
if (staleDataPath != null) {
if (!isUpToDate(cmd)) {
doExecuteJavadocCommandLine(cmd, javadocOutputDirectory);
StaleHelper.writeStaleData(cmd, staleDataPath.toPath());
}
} else {
doExecuteJavadocCommandLine(cmd, javadocOutputDirectory);
}
}
/**
* Check if the javadoc is uptodate or not
*
* @param cmd not null
* @return true is the javadoc is uptodate, false otherwise
* @throws MavenReportException if any error occur
*/
private boolean isUpToDate(Commandline cmd) throws MavenReportException {
try {
String curdata = StaleHelper.getStaleData(cmd);
Path cacheData = staleDataPath.toPath();
String prvdata;
if (Files.isRegularFile(cacheData)) {
prvdata = new String(Files.readAllBytes(cacheData), StandardCharsets.UTF_8);
} else {
prvdata = null;
}
if (curdata.equals(prvdata)) {
getLog().info("Skipping javadoc generation, everything is up to date.");
return true;
} else {
if (prvdata == null) {
getLog().info("No previous run data found, generating javadoc.");
} else {
getLog().info("Configuration changed, re-generating javadoc.");
}
}
} catch (IOException e) {
throw new MavenReportException("Error checking uptodate status", e);
}
return false;
}
/**
* Execute the Javadoc command line
*
* @param cmd not null
* @param javadocOutputDirectory not null
* @throws MavenReportException if any errors occur
*/
private void doExecuteJavadocCommandLine(Commandline cmd, File javadocOutputDirectory) throws MavenReportException {
if (getLog().isDebugEnabled()) {
// no quoted arguments
getLog().debug(CommandLineUtils.toString(cmd.getCommandline()).replaceAll("'", ""));
}
String cmdLine = null;
if (debug) {
cmdLine = CommandLineUtils.toString(cmd.getCommandline()).replaceAll("'", "");
writeDebugJavadocScript(cmdLine, javadocOutputDirectory);
}
CommandLineUtils.StringStreamConsumer err = new JavadocUtil.JavadocOutputStreamConsumer();
CommandLineUtils.StringStreamConsumer out = new JavadocUtil.JavadocOutputStreamConsumer();
try {
int exitCode = CommandLineUtils.executeCommandLine(cmd, out, err);
String output = StringUtils.isEmpty(out.getOutput())
? null
: '\n' + out.getOutput().trim();
if (exitCode != 0) {
if (cmdLine == null) {
cmdLine = CommandLineUtils.toString(cmd.getCommandline()).replaceAll("'", "");
}
writeDebugJavadocScript(cmdLine, javadocOutputDirectory);
if ((output != null && !output.isEmpty())
&& StringUtils.isEmpty(err.getOutput())
&& isJavadocVMInitError(output)) {
throw new MavenReportException(output + '\n' + '\n' + JavadocUtil.ERROR_INIT_VM + '\n'
+ "Or, try to reduce the Java heap size for the Javadoc goal using "
+ "-Dminmemory= and -Dmaxmemory=." + '\n' + '\n' + "Command line was: "
+ cmdLine
+ '\n' + '\n' + "Refer to the generated Javadoc files in '" + javadocOutputDirectory
+ "' dir.\n");
}
if (output != null && !output.isEmpty()) {
getLog().info(output);
}
StringBuilder msg = new StringBuilder("\nExit code: ");
msg.append(exitCode);
if (StringUtils.isNotEmpty(err.getOutput())) {
// parse stderr, log informational output, add all other to exception message
List nonInfoLines = new ArrayList<>();
for (String str : err.getOutput().split("\\R")) {
if (isInformationalOutput(str)) {
getLog().debug(str);
} else {
nonInfoLines.add(str);
}
}
if (!nonInfoLines.isEmpty()) {
msg.append('\n'); // new line between exit code and warnings/errors
msg.append(String.join("\n", nonInfoLines));
}
}
msg.append('\n');
msg.append("Command line was: ").append(cmdLine).append('\n').append('\n');
msg.append("Refer to the generated Javadoc files in '")
.append(javadocOutputDirectory)
.append("' dir.\n");
throw new MavenReportException(msg.toString());
}
if (output != null && !output.isEmpty()) {
getLog().info(output);
}
} catch (CommandLineException e) {
throw new MavenReportException("Unable to execute javadoc command: " + e.getMessage(), e);
}
// ----------------------------------------------------------------------
// Handle Javadoc warnings
// ----------------------------------------------------------------------
if (containsWarnings(err.getOutput())) {
if (getLog().isWarnEnabled()) {
getLog().warn("Javadoc Warnings");
StringTokenizer token = new StringTokenizer(err.getOutput(), "\n");
while (token.hasMoreTokens()) {
String current = token.nextToken().trim();
// log informational output at debug level only
if (isInformationalOutput(current)) {
getLog().debug(current);
} else {
getLog().warn(current);
}
}
}
if (failOnWarnings) {
throw new MavenReportException("Project contains Javadoc Warnings");
}
}
}
private boolean containsWarnings(String output) {
// JDK-8268774 / JDK-8270831
if (this.javadocRuntimeVersion.isBefore("17")) {
return output != null && !output.isEmpty();
} else {
return Arrays.stream(output.split("\\R"))
.reduce((first, second) -> second) // last line
.filter(line -> line.matches("\\d+ warnings?"))
.isPresent();
}
}
/**
* Determines whether the specified string is informational output of the Javadoc tool.
* Such output should not be included as exception message or logged as warning or error.
*
* The following texts are either hardcoded in the tool or can be found in versions of the
* javadoc tool's English resource bundle of JDK 11 (and presumably later versions).
* This method will neither help nor harm for localized (non-English) versions of the tool.
*
*
* @param str string to check
* @return true if informational output, false if not or cannot be determined
*/
private boolean isInformationalOutput(String str) {
return str == null
|| str.trim().isEmpty()
|| str.startsWith("Loading source files for package ") // main.Loading_source_files_for_package
|| str.startsWith("Loading source file ") // main.Loading_source_file
|| str.startsWith("Generating ")
|| str.startsWith("Constructing Javadoc information") // main.Building_tree
|| str.startsWith("Building index for ")
|| str.startsWith("Building tree for ")
|| str.startsWith("Standard Doclet version ");
}
/**
* Patches the given Javadoc output directory to work around CVE-2013-1571
* (see http://www.kb.cert.org/vuls/id/225657).
*
* @param javadocOutputDirectory directory to scan for vulnerabilities
* @param outputEncoding encoding used by the javadoc tool (-docencoding parameter).
* If {@code null}, the platform's default encoding is used (like javadoc does).
* @return the number of patched files
*/
private int fixFrameInjectionBug(File javadocOutputDirectory, String outputEncoding) throws IOException {
final String fixData;
try (InputStream in = this.getClass().getResourceAsStream("frame-injection-fix.txt")) {
if (in == null) {
throw new FileNotFoundException("Missing resource 'frame-injection-fix.txt' in classpath.");
}
fixData = org.codehaus.plexus.util.StringUtils.unifyLineSeparators(IOUtil.toString(in, "US-ASCII"))
.trim();
}
final DirectoryScanner ds = new DirectoryScanner();
ds.setBasedir(javadocOutputDirectory);
ds.setCaseSensitive(false);
ds.setIncludes(new String[] {"**/index.html", "**/index.htm", "**/toc.html", "**/toc.htm"});
ds.addDefaultExcludes();
ds.scan();
int patched = 0;
for (String f : ds.getIncludedFiles()) {
final File file = new File(javadocOutputDirectory, f);
// we load the whole file as one String (toc/index files are
// generally small, because they only contain frameset declaration):
final String fileContents = FileUtils.fileRead(file, outputEncoding);
// check if file may be vulnerable because it was not patched with "validURL(url)":
if (!StringUtils.contains(fileContents, "function validURL(url) {")) {
// we need to patch the file!
final String patchedFileContents =
StringUtils.replaceOnce(fileContents, "function loadFrames() {", fixData);
if (!patchedFileContents.equals(fileContents)) {
FileUtils.fileWrite(file, outputEncoding, patchedFileContents);
patched++;
}
}
}
return patched;
}
/**
* @param outputFile not nul
* @param inputResourceName a not null resource in src/main/java, src/main/resources or
* src/main/javadoc or in the Javadoc plugin dependencies.
* @return the resource file absolute path as String
* @since 2.6
*/
private Optional getResource(File outputFile, String inputResourceName) {
if (inputResourceName.startsWith("/")) {
inputResourceName = inputResourceName.replaceFirst("//*", "");
}
List classPath = new ArrayList<>();
classPath.add(project.getBuild().getSourceDirectory());
URL resourceURL = getResource(classPath, inputResourceName);
if (resourceURL != null) {
getLog().debug(inputResourceName + " found in the main src directory of the project.");
return Optional.of(FileUtils.toFile(resourceURL));
}
classPath.clear();
List resources = project.getBuild().getResources();
for (Resource resource : resources) {
classPath.add(resource.getDirectory());
}
resourceURL = getResource(classPath, inputResourceName);
if (resourceURL != null) {
getLog().debug(inputResourceName + " found in the main resources directories of the project.");
return Optional.of(FileUtils.toFile(resourceURL));
}
if (javadocDirectory.exists()) {
classPath.clear();
classPath.add(javadocDirectory.getAbsolutePath());
resourceURL = getResource(classPath, inputResourceName);
if (resourceURL != null) {
getLog().debug(inputResourceName + " found in the main javadoc directory of the project.");
return Optional.of(FileUtils.toFile(resourceURL));
}
}
classPath.clear();
final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
Plugin javadocPlugin = getPlugin(project, pluginId);
if (javadocPlugin != null && javadocPlugin.getDependencies() != null) {
List dependencies = javadocPlugin.getDependencies();
for (Dependency dependency : dependencies) {
ResourcesArtifact resourceArtifact = new ResourcesArtifact();
resourceArtifact.setGroupId(dependency.getGroupId());
resourceArtifact.setArtifactId(dependency.getArtifactId());
resourceArtifact.setVersion(dependency.getVersion());
resourceArtifact.setClassifier(dependency.getClassifier());
Artifact artifact = null;
try {
artifact = createAndResolveArtifact(resourceArtifact);
} catch (Exception e) {
logError("Unable to retrieve the dependency: " + dependency + ". Ignored.", e);
}
if (artifact != null && artifact.getFile().exists()) {
classPath.add(artifact.getFile().getAbsolutePath());
}
}
resourceURL = getResource(classPath, inputResourceName);
if (resourceURL != null) {
getLog().debug(inputResourceName + " found in javadoc plugin dependencies.");
try {
JavadocUtil.copyResource(resourceURL, outputFile);
return Optional.of(outputFile);
} catch (IOException e) {
logError("IOException: " + e.getMessage(), e);
}
}
}
getLog().warn("Unable to find the resource '" + inputResourceName + "'. Using default Javadoc resources.");
return Optional.empty();
}
/**
* @param classPath a not null String list of files where resource will be looked up
* @param resource a not null resource to find in the class path
* @return the resource from the given classpath or null if not found
* @see ClassLoader#getResource(String)
* @since 2.6
*/
private URL getResource(final List classPath, final String resource) {
List urls = new ArrayList<>(classPath.size());
for (String filename : classPath) {
try {
urls.add(new File(filename).toURI().toURL());
} catch (MalformedURLException e) {
getLog().error("MalformedURLException: " + e.getMessage());
}
}
URLClassLoader javadocClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), null);
try {
return javadocClassLoader.getResource(resource);
} finally {
try {
javadocClassLoader.close();
} catch (IOException ex) {
// ignore
}
}
}
/**
* Get the full javadoc goal. Loads the plugin's pom.properties to get the current plugin version.
*
* @return org.apache.maven.plugins:maven-javadoc-plugin:CURRENT_VERSION:[test-]javadoc
*/
private String getFullJavadocGoal() {
String javadocPluginVersion = null;
String resource = "META-INF/maven/org.apache.maven.plugins/maven-javadoc-plugin/pom.properties";
try (InputStream resourceAsStream =
AbstractJavadocMojo.class.getClassLoader().getResourceAsStream(resource)) {
if (resourceAsStream != null) {
Properties properties = new Properties();
properties.load(resourceAsStream);
if (StringUtils.isNotEmpty(properties.getProperty("version"))) {
javadocPluginVersion = properties.getProperty("version");
}
}
} catch (IOException e) {
// nop
}
StringBuilder sb = new StringBuilder();
sb.append("org.apache.maven.plugins:maven-javadoc-plugin:");
if (javadocPluginVersion != null && !javadocPluginVersion.isEmpty()) {
sb.append(javadocPluginVersion).append(":");
}
if (this instanceof TestJavadocReport) {
sb.append("test-javadoc");
} else {
sb.append("javadoc");
}
return sb.toString();
}
/**
* Using Maven, a Javadoc link is given by ${project.url}/apidocs.
*
* @return the detected Javadoc links using the Maven conventions for all modules defined in the current project
* or an empty list
* @throws MavenReportException if any
* @see #detectOfflineLinks
* @see #reactorProjects
* @since 2.6
*/
private List getModulesLinks() throws MavenReportException {
List aggregatedProjects = reactorProjects;
if (!detectOfflineLinks || isAggregator() || aggregatedProjects.isEmpty()) {
return Collections.emptyList();
}
getLog().debug("Trying to add links for modules...");
Set dependencyArtifactIds = new HashSet<>();
final Set dependencyArtifacts = project.getDependencyArtifacts();
for (Artifact artifact : dependencyArtifacts) {
dependencyArtifactIds.add(artifact.getId());
}
List modulesLinks = new ArrayList<>();
String javadocDirRelative = PathUtils.toRelative(project.getBasedir(), getOutputDirectory());
for (MavenProject p : aggregatedProjects) {
if (!dependencyArtifactIds.contains(p.getArtifact().getId()) || (p.getUrl() == null)) {
continue;
}
File location = new File(p.getBasedir(), javadocDirRelative);
if (!location.exists()) {
if (getLog().isDebugEnabled()) {
getLog().debug("Javadoc directory not found: " + location);
}
String javadocGoal = getFullJavadocGoal();
getLog().info("The goal '" + javadocGoal + "' has not been previously called for the module: '"
+ p.getId() + "'. Trying to invoke it...");
File invokerDir = new File(project.getBuild().getDirectory(), "invoker");
invokerDir.mkdirs();
File invokerLogFile = FileUtils.createTempFile("maven-javadoc-plugin", ".txt", invokerDir);
try {
JavadocUtil.invokeMaven(
getLog(),
session.getRepositorySession().getLocalRepository().getBasedir(),
p.getFile(),
Collections.singletonList(javadocGoal),
null,
invokerLogFile,
session.getRequest().getGlobalSettingsFile());
} catch (MavenInvocationException e) {
logError("MavenInvocationException: " + e.getMessage(), e);
String invokerLogContent = JavadocUtil.readFile(invokerLogFile, null /* platform encoding */);
// TODO: Why are we only interested in cases where the JVM won't start?
// [MJAVADOC-275][jdcasey] I changed the logic here to only throw an error WHEN
// the JVM won't start (opposite of what it was).
if (invokerLogContent != null && invokerLogContent.contains(JavadocUtil.ERROR_INIT_VM)) {
throw new MavenReportException(e.getMessage(), e);
}
} finally {
// just create the directory to prevent repeated invocations..
if (!location.exists()) {
getLog().warn("Creating fake javadoc directory to prevent repeated invocations: " + location);
location.mkdirs();
}
}
}
if (location.exists()) {
String url = getJavadocLink(p);
OfflineLink ol = new OfflineLink();
ol.setUrl(url);
ol.setLocation(location.getAbsolutePath());
if (getLog().isDebugEnabled()) {
getLog().debug("Added Javadoc offline link: " + url + " for the module: " + p.getId());
}
modulesLinks.add(ol);
}
}
return modulesLinks;
}
/**
* Using Maven, a Javadoc link is given by ${project.url}/apidocs.
*
* @return the detected Javadoc links using the Maven conventions for all dependencies defined in the current
* project or an empty list.
* @see #detectLinks
* @see #isValidJavadocLink(String, boolean)
* @since 2.6
*/
private List getDependenciesLinks() {
if (!detectLinks) {
return Collections.emptyList();
}
getLog().debug("Trying to add links for dependencies...");
List dependenciesLinks = new ArrayList<>();
final Set dependencies = project.getDependencyArtifacts();
for (Artifact artifact : dependencies) {
if (artifact.getFile() == null || !artifact.getFile().exists()) {
continue;
}
Optional depLink = this.dependencyLinks.stream()
.filter(d -> matches(d, artifact))
.findAny();
final String url;
final boolean detected;
if (depLink.isPresent()) {
url = depLink.get().getUrl();
detected = false;
} else {
try {
MavenProject artifactProject = mavenProjectBuilder
.build(artifact, getProjectBuildingRequest(project))
.getProject();
url = getJavadocLink(artifactProject);
detected = true;
} catch (ProjectBuildingException e) {
logError("ProjectBuildingException for " + artifact.toString() + ": " + e.getMessage(), e);
continue;
}
}
if (url != null && isValidJavadocLink(url, detected)) {
getLog().debug("Added Javadoc link: " + url + " for " + artifact.getId());
dependenciesLinks.add(url);
}
}
return dependenciesLinks;
}
private boolean matches(DependencyLink d, Artifact artifact) {
if (d.getGroupId() != null && !d.getGroupId().equals(artifact.getGroupId())) {
return false;
}
if (d.getArtifactId() != null && !d.getArtifactId().equals(artifact.getArtifactId())) {
return false;
}
if (d.getClassifier() != null && !d.getClassifier().equals(artifact.getClassifier())) {
return false;
}
return true;
}
/**
* @return if {@code detectJavaApiLink}, the Java API link based on the {@code javaApiLinks} properties and the
* value of the source parameter in the
* org.apache.maven.plugins:maven-compiler-plugin
* defined in ${project.build.plugins} or in ${project.build.pluginManagement},
* or the {@code javadocRuntimeVersion}, or null if not defined.
* @see source parameter
* @since 2.6
*/
protected final OfflineLink getDefaultJavadocApiLink() {
if (!detectJavaApiLink) {
return null;
}
final JavaVersion javaApiversion;
if (release != null) {
javaApiversion = JavaVersion.parse(release);
} else if (source != null && !source.isEmpty()) {
javaApiversion = JavaVersion.parse(source);
} else {
final String pluginId = "org.apache.maven.plugins:maven-compiler-plugin";
String sourceConfigured = getPluginParameter(project, pluginId, "source");
if (sourceConfigured != null) {
javaApiversion = JavaVersion.parse(sourceConfigured);
} else {
getLog().debug("No maven-compiler-plugin defined in ${build.plugins} or in "
+ "${project.build.pluginManagement} for the " + project.getId()
+ ". Added Javadoc API link according the javadoc executable version i.e.: "
+ javadocRuntimeVersion);
javaApiversion = javadocRuntimeVersion;
}
}
final String javaApiKey;
if (javaApiversion.asMajor().isAtLeast("9")) {
javaApiKey = "api_" + javaApiversion.asMajor();
} else {
javaApiKey = "api_1." + javaApiversion.asMajor().toString().charAt(0);
}
final String javaApiLink;
if (javaApiLinks != null && javaApiLinks.containsKey(javaApiKey)) {
javaApiLink = javaApiLinks.getProperty(javaApiKey);
} else if (javaApiversion.isAtLeast("16")) {
javaApiLink = null; // JDK-8216497
} else if (javaApiversion.isAtLeast("11")) {
javaApiLink =
String.format("https://docs.oracle.com/en/java/javase/%s/docs/api/", javaApiversion.getValue(1));
} else if (javaApiversion.asMajor().isAtLeast("6")) {
javaApiLink = String.format(
"https://docs.oracle.com/javase/%s/docs/api/",
javaApiversion.asMajor().getValue(1));
} else if (javaApiversion.isAtLeast("1.5")) {
javaApiLink = "https://docs.oracle.com/javase/1.5.0/docs/api/";
} else {
javaApiLink = null;
}
if (getLog().isDebugEnabled()) {
if (javaApiLink != null) {
getLog().debug("Found Java API link: " + javaApiLink);
} else {
getLog().debug("No Java API link found.");
}
}
if (javaApiLink == null) {
return null;
}
final Path javaApiListFile;
final String resourceName;
if (javaApiversion.isAtLeast("10")) {
javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve("element-list");
resourceName = "java-api-element-list-" + javaApiversion.toString().substring(0, 2);
} else if (javaApiversion.asMajor().isAtLeast("9")) {
javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve("package-list");
resourceName = "java-api-package-list-9";
} else {
javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve("package-list");
resourceName = "java-api-package-list-1."
+ javaApiversion.asMajor().toString().charAt(0);
}
OfflineLink link = new OfflineLink();
link.setLocation(javaApiListFile.getParent().toAbsolutePath().toString());
link.setUrl(javaApiLink);
InputStream in = this.getClass().getResourceAsStream(resourceName);
if (in != null) {
try (InputStream closableIS = in) {
// TODO only copy when changed
Files.copy(closableIS, javaApiListFile, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ioe) {
logError("Can't get " + resourceName + ": " + ioe.getMessage(), ioe);
return null;
}
}
return link;
}
/**
* Follows all of the given links, and returns their last redirect locations. Ordering is kept.
* This is necessary because javadoc tool doesn't follow links, see JDK-8190312 (MJAVADOC-427, MJAVADOC-487)
*
* @param links Links to follow.
* @return Last redirect location of all the links.
*/
private Set followLinks(Set links) {
Set redirectLinks = new LinkedHashSet<>(links.size());
for (String link : links) {
try {
redirectLinks.add(JavadocUtil.getRedirectUrl(new URI(link).toURL(), settings)
.toString());
} catch (Exception e) {
// only print in debug, it should have been logged already in warn/error because link isn't valid
getLog().debug("Could not follow " + link + ". Reason: " + e.getMessage());
// Even when link produces error it should be kept in the set because the error might be caused by
// incomplete redirect configuration on the server side.
// This partially restores the previous behaviour before fix for MJAVADOC-427
redirectLinks.add(link);
}
}
return redirectLinks;
}
/**
* @param link not null
* @param detecting true if the link is generated by
* detectLinks, or false otherwise
* @return true if the link has a /package-list, false otherwise.
* @see
* package-list spec
* @since 2.6
*/
protected boolean isValidJavadocLink(String link, boolean detecting) {
try {
final URI packageListUri;
final URI elementListUri;
if (link.trim().toLowerCase(Locale.ENGLISH).startsWith("http:")
|| link.trim().toLowerCase(Locale.ENGLISH).startsWith("https:")
|| link.trim().toLowerCase(Locale.ENGLISH).startsWith("ftp:")
|| link.trim().toLowerCase(Locale.ENGLISH).startsWith("file:")) {
packageListUri = new URI(link + '/' + PACKAGE_LIST);
elementListUri = new URI(link + '/' + ELEMENT_LIST);
} else {
// links can be relative paths or files
File dir = new File(link);
if (!dir.isAbsolute()) {
dir = new File(getOutputDirectory(), link);
}
if (!dir.isDirectory()) {
if (detecting) {
getLog().warn("The given File link: " + dir + " is not a dir.");
} else {
getLog().error("The given File link: " + dir + " is not a dir.");
}
}
packageListUri = new File(dir, PACKAGE_LIST).toURI();
elementListUri = new File(dir, ELEMENT_LIST).toURI();
}
try {
if (JavadocUtil.isValidElementList(elementListUri.toURL(), settings, validateLinks)) {
return true;
}
} catch (IOException e) {
}
if (JavadocUtil.isValidPackageList(packageListUri.toURL(), settings, validateLinks)) {
return true;
}
if (getLog().isErrorEnabled()) {
if (detecting) {
getLog().warn("Invalid links: " + link + " with /" + PACKAGE_LIST + " or / " + ELEMENT_LIST
+ ". Ignored it.");
} else {
getLog().error("Invalid links: " + link + " with /" + PACKAGE_LIST + " or / " + ELEMENT_LIST
+ ". Ignored it.");
}
}
return false;
} catch (URISyntaxException e) {
if (getLog().isErrorEnabled()) {
if (detecting) {
getLog().warn("Malformed link: " + e.getInput() + ". Ignored it.");
} else {
getLog().error("Malformed link: " + e.getInput() + ". Ignored it.");
}
}
return false;
} catch (IOException e) {
if (getLog().isErrorEnabled()) {
if (detecting) {
getLog().warn("Error fetching link: " + link + ". Ignored it.");
} else {
getLog().error("Error fetching link: " + link + ". Ignored it.");
}
}
return false;
}
}
/**
* Write a debug javadoc script in case of command line error or in debug mode.
*
* @param cmdLine the current command line as string, not null.
* @param javadocOutputDirectory the output dir, not null.
* @see #executeJavadocCommandLine(Commandline, File)
* @since 2.6
*/
private void writeDebugJavadocScript(String cmdLine, File javadocOutputDirectory) {
File commandLineFile = new File(javadocOutputDirectory, DEBUG_JAVADOC_SCRIPT_NAME);
commandLineFile.getParentFile().mkdirs();
try {
FileUtils.fileWrite(commandLineFile.getAbsolutePath(), null /* platform encoding */, cmdLine);
if (!SystemUtils.IS_OS_WINDOWS) {
Runtime.getRuntime().exec(new String[] {"chmod", "a+x", commandLineFile.getAbsolutePath()});
}
} catch (IOException e) {
logError("Unable to write '" + commandLineFile.getName() + "' debug script file", e);
}
}
/**
* Check if the Javadoc JVM is correctly started or not.
*
* @param output the command line output, not null.
* @return true if Javadoc output command line contains Javadoc word, false otherwise.
* @see #executeJavadocCommandLine(Commandline, File)
* @since 2.6.1
*/
private boolean isJavadocVMInitError(String output) {
/*
* see main.usage and main.Building_tree keys from
* com.sun.tools.javadoc.resources.javadoc bundle in tools.jar
*/
return !(output.contains("Javadoc") || output.contains("javadoc"));
}
// ----------------------------------------------------------------------
// Static methods
// ----------------------------------------------------------------------
/**
* @param p not null
* @return the javadoc link based on the project url i.e. ${project.url}/${destDir} where
* destDir is configued in the Javadoc plugin configuration (apidocs by default).
* @since 2.6
*/
private static String getJavadocLink(MavenProject p) {
if (p.getUrl() == null) {
return null;
}
String url = cleanUrl(p.getUrl());
String destDir = "apidocs"; // see JavadocReport#destDir
final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
String destDirConfigured = getPluginParameter(p, pluginId, "destDir");
if (destDirConfigured != null) {
destDir = destDirConfigured;
}
return url + "/" + destDir;
}
/**
* @param url could be null.
* @return the url cleaned or empty if url was null.
* @since 2.6
*/
private static String cleanUrl(String url) {
if (url == null) {
return "";
}
url = url.trim();
while (url.endsWith("/")) {
url = url.substring(0, url.lastIndexOf("/"));
}
return url;
}
/**
* @param p not null
* @param pluginId not null key of the plugin defined in {@link org.apache.maven.model.Build#getPluginsAsMap()}
* or in {@link org.apache.maven.model.PluginManagement#getPluginsAsMap()}
* @return the Maven plugin defined in ${project.build.plugins} or in
* ${project.build.pluginManagement}, or null if not defined.
* @since 2.6
*/
private static Plugin getPlugin(MavenProject p, String pluginId) {
if ((p.getBuild() == null) || (p.getBuild().getPluginsAsMap() == null)) {
return null;
}
Plugin plugin = p.getBuild().getPluginsAsMap().get(pluginId);
if ((plugin == null)
&& (p.getBuild().getPluginManagement() != null)
&& (p.getBuild().getPluginManagement().getPluginsAsMap() != null)) {
plugin = p.getBuild().getPluginManagement().getPluginsAsMap().get(pluginId);
}
return plugin;
}
/**
* @param p not null
* @param pluginId not null
* @param param not null
* @return the simple parameter as String defined in the plugin configuration by param key
* or null if not found.
* @since 2.6
*/
private static String getPluginParameter(MavenProject p, String pluginId, String param) {
// p.getGoalConfiguration( pluginGroupId, pluginArtifactId, executionId, goalId );
Plugin plugin = getPlugin(p, pluginId);
if (plugin != null) {
Xpp3Dom xpp3Dom = (Xpp3Dom) plugin.getConfiguration();
if (xpp3Dom != null
&& xpp3Dom.getChild(param) != null
&& StringUtils.isNotEmpty(xpp3Dom.getChild(param).getValue())) {
return xpp3Dom.getChild(param).getValue();
}
}
return null;
}
/**
* Construct the output file for the generated javadoc-options XML file, after creating the
* javadocOptionsDir if necessary. This method does NOT write to the file in question.
*
* @return The options {@link File} file.
* @since 2.7
*/
protected final File getJavadocOptionsFile() {
if (javadocOptionsDir != null && !javadocOptionsDir.exists()) {
javadocOptionsDir.mkdirs();
}
return new File(javadocOptionsDir, "javadoc-options-" + getAttachmentClassifier() + ".xml");
}
/**
* Generate a javadoc-options XML file, for either bundling with a javadoc-resources artifact OR
* supplying to a distro module in a includeDependencySources configuration, so the javadoc options
* from this execution can be reconstructed and merged in the distro build.
*
* @return {@link JavadocOptions}
* @throws IOException {@link IOException}
* @since 2.7
*/
protected final JavadocOptions buildJavadocOptions() throws IOException {
JavadocOptions options = new JavadocOptions();
options.setBootclasspathArtifacts(toList(bootclasspathArtifacts));
options.setDocfilesSubdirsUsed(docfilessubdirs);
options.setDocletArtifacts(toList(docletArtifact, docletArtifacts));
options.setExcludedDocfilesSubdirs(excludedocfilessubdir);
options.setExcludePackageNames(toList(excludePackageNames));
options.setGroups(toList(groups));
options.setLinks(links);
options.setOfflineLinks(toList(offlineLinks));
options.setResourcesArtifacts(toList(resourcesArtifacts));
options.setTagletArtifacts(toList(tagletArtifact, tagletArtifacts));
options.setTaglets(toList(taglets));
options.setTags(toList(tags));
if (getProject() != null && getJavadocDirectory() != null) {
options.setJavadocResourcesDirectory(
toRelative(getProject().getBasedir(), getJavadocDirectory().getAbsolutePath()));
}
File optionsFile = getJavadocOptionsFile();
try (Writer writer = WriterFactory.newXmlWriter(optionsFile)) {
new JavadocOptionsXpp3Writer().write(writer, options);
}
return options;
}
/**
* Override this if you need to provide a bundle attachment classifier, as in the case of test
* javadocs.
* @return The attachment classifier.
*/
protected String getAttachmentClassifier() {
return JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER;
}
/**
* Logs an error with throwable content only if in debug.
*
* @param message The message which should be announced.
* @param t The throwable part of the message.
*/
protected void logError(String message, Throwable t) {
if (getLog().isDebugEnabled()) {
getLog().error(message, t);
} else {
getLog().error(message);
}
}
/**
* @param prefix The prefix of the exception.
* @param e The exception.
* @throws MojoExecutionException {@link MojoExecutionException} issue while generating report
*/
protected void failOnError(String prefix, Exception e) throws MojoExecutionException {
if (failOnError) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new MojoExecutionException(prefix + ": " + e.getMessage(), e);
}
getLog().error(prefix + ": " + e.getMessage(), e);
}
/**
* @return list of projects to be part of aggregated javadoc
*/
private List getAggregatedProjects() {
if (this.reactorProjects == null) {
return Collections.emptyList();
}
Map reactorProjectsMap = new HashMap<>();
for (MavenProject reactorProject : this.reactorProjects) {
if (!isSkippedJavadoc(reactorProject)
&& //
!isSkippedModule(reactorProject)) {
reactorProjectsMap.put(reactorProject.getBasedir().toPath(), reactorProject);
}
}
return new ArrayList<>(modulesForAggregatedProject(project, reactorProjectsMap));
}
/**
* @param mavenProject the project that might be skipped
* @return true if the project needs to be skipped from aggregate generation
*/
protected boolean isSkippedModule(MavenProject mavenProject) {
if (this.skippedModules == null || this.skippedModules.isEmpty()) {
return false;
}
List modulesToSkip = Arrays.asList(StringUtils.split(this.skippedModules, ','));
return modulesToSkip.contains(mavenProject.getArtifactId());
}
/**
* @param mavenProject the project that might be skipped
* @return true if the pom configuration skips javadoc generation for the project
*/
protected boolean isSkippedJavadoc(MavenProject mavenProject) {
String property = mavenProject.getProperties().getProperty("maven.javadoc.skip");
if (property != null) {
boolean skip = BooleanUtils.toBoolean(property);
getLog().debug("isSkippedJavadoc " + mavenProject + " " + skip);
return skip;
}
final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
property = getPluginParameter(mavenProject, pluginId, "skip");
if (property != null) {
boolean skip = BooleanUtils.toBoolean(property);
getLog().debug("isSkippedJavadoc " + mavenProject + " " + skip);
return skip;
}
if (mavenProject.getParent() != null) {
return isSkippedJavadoc(mavenProject.getParent());
}
getLog().debug("isSkippedJavadoc " + mavenProject + " " + false);
return false;
}
}