View Javadoc
1   package org.apache.maven.plugins.javadoc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.lang3.BooleanUtils;
23  import org.apache.commons.lang3.ClassUtils;
24  import org.apache.commons.lang3.StringUtils;
25  import org.apache.maven.artifact.Artifact;
26  import org.apache.maven.artifact.ArtifactUtils;
27  import org.apache.maven.artifact.handler.ArtifactHandler;
28  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
29  import org.apache.maven.artifact.repository.ArtifactRepository;
30  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
31  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
32  import org.apache.maven.artifact.versioning.ArtifactVersion;
33  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
34  import org.apache.maven.execution.MavenSession;
35  import org.apache.maven.model.Dependency;
36  import org.apache.maven.model.Plugin;
37  import org.apache.maven.model.Resource;
38  import org.apache.maven.plugin.AbstractMojo;
39  import org.apache.maven.plugin.MojoExecution;
40  import org.apache.maven.plugin.MojoExecutionException;
41  import org.apache.maven.plugin.MojoFailureException;
42  import org.apache.maven.plugins.annotations.Component;
43  import org.apache.maven.plugins.annotations.Parameter;
44  import org.apache.maven.plugins.javadoc.options.BootclasspathArtifact;
45  import org.apache.maven.plugins.javadoc.options.DocletArtifact;
46  import org.apache.maven.plugins.javadoc.options.Group;
47  import org.apache.maven.plugins.javadoc.options.JavadocOptions;
48  import org.apache.maven.plugins.javadoc.options.JavadocPathArtifact;
49  import org.apache.maven.plugins.javadoc.options.OfflineLink;
50  import org.apache.maven.plugins.javadoc.options.ResourcesArtifact;
51  import org.apache.maven.plugins.javadoc.options.Tag;
52  import org.apache.maven.plugins.javadoc.options.Taglet;
53  import org.apache.maven.plugins.javadoc.options.TagletArtifact;
54  import org.apache.maven.plugins.javadoc.options.io.xpp3.JavadocOptionsXpp3Writer;
55  import org.apache.maven.plugins.javadoc.resolver.JavadocBundle;
56  import org.apache.maven.plugins.javadoc.resolver.ResourceResolver;
57  import org.apache.maven.plugins.javadoc.resolver.SourceResolverConfig;
58  import org.apache.maven.project.DefaultProjectBuildingRequest;
59  import org.apache.maven.project.MavenProject;
60  import org.apache.maven.project.ProjectBuilder;
61  import org.apache.maven.project.ProjectBuildingException;
62  import org.apache.maven.project.ProjectBuildingRequest;
63  import org.apache.maven.reporting.MavenReportException;
64  import org.apache.maven.settings.Proxy;
65  import org.apache.maven.settings.Settings;
66  import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;
67  import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
68  import org.apache.maven.shared.artifact.filter.resolve.PatternExclusionsFilter;
69  import org.apache.maven.shared.artifact.filter.resolve.PatternInclusionsFilter;
70  import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
71  import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
72  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
73  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
74  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
75  import org.apache.maven.shared.transfer.dependencies.DefaultDependableCoordinate;
76  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
77  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException;
78  import org.apache.maven.shared.invoker.MavenInvocationException;
79  import org.apache.maven.toolchain.Toolchain;
80  import org.apache.maven.toolchain.ToolchainManager;
81  import org.apache.maven.wagon.PathUtils;
82  import org.codehaus.plexus.archiver.ArchiverException;
83  import org.codehaus.plexus.archiver.UnArchiver;
84  import org.codehaus.plexus.archiver.manager.ArchiverManager;
85  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
86  import org.codehaus.plexus.components.io.fileselectors.IncludeExcludeFileSelector;
87  import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
88  import org.codehaus.plexus.languages.java.jpms.LocationManager;
89  import org.codehaus.plexus.languages.java.jpms.ModuleNameSource;
90  import org.codehaus.plexus.languages.java.jpms.ResolvePathRequest;
91  import org.codehaus.plexus.languages.java.jpms.ResolvePathResult;
92  import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
93  import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
94  import org.codehaus.plexus.languages.java.version.JavaVersion;
95  import org.codehaus.plexus.util.DirectoryScanner;
96  import org.codehaus.plexus.util.FileUtils;
97  import org.codehaus.plexus.util.IOUtil;
98  import org.codehaus.plexus.util.ReaderFactory;
99  import org.codehaus.plexus.util.WriterFactory;
100 import org.codehaus.plexus.util.cli.CommandLineException;
101 import org.codehaus.plexus.util.cli.CommandLineUtils;
102 import org.codehaus.plexus.util.cli.Commandline;
103 import org.codehaus.plexus.util.xml.Xpp3Dom;
104 
105 import java.io.File;
106 import java.io.FileNotFoundException;
107 import java.io.IOException;
108 import java.io.InputStream;
109 import java.io.Writer;
110 import java.lang.reflect.Method;
111 import java.net.MalformedURLException;
112 import java.net.URI;
113 import java.net.URISyntaxException;
114 import java.net.URL;
115 import java.net.URLClassLoader;
116 import java.nio.charset.StandardCharsets;
117 import java.nio.file.Files;
118 import java.nio.file.Path;
119 import java.nio.file.StandardCopyOption;
120 import java.time.format.DateTimeFormatter;
121 import java.time.temporal.ChronoField;
122 import java.util.ArrayList;
123 import java.util.Arrays;
124 import java.util.Calendar;
125 import java.util.Collection;
126 import java.util.Collections;
127 import java.util.HashMap;
128 import java.util.HashSet;
129 import java.util.LinkedHashMap;
130 import java.util.LinkedHashSet;
131 import java.util.LinkedList;
132 import java.util.List;
133 import java.util.Locale;
134 import java.util.Map;
135 import java.util.Map.Entry;
136 import java.util.Optional;
137 import java.util.Properties;
138 import java.util.Set;
139 import java.util.StringTokenizer;
140 import java.util.stream.Collectors;
141 
142 import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
143 import static org.apache.maven.plugins.javadoc.JavadocUtil.toRelative;
144 import static org.apache.maven.plugins.javadoc.JavadocUtil.toList;
145 import static org.apache.maven.plugins.javadoc.JavadocUtil.isEmpty;
146 import static org.apache.maven.plugins.javadoc.JavadocUtil.isNotEmpty;
147 
148 /**
149  * Base class with majority of Javadoc functionalities.
150  *
151  * @author <a href="mailto:brett@apache.org">Brett Porter</a>
152  * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
153  * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html">
154  *      The Java API Documentation Generator, 7</a>
155  * @since 2.0
156  */
157 public abstract class AbstractJavadocMojo
158     extends AbstractMojo
159 {
160     /**
161      * Property with timestamp used for reproducible builds
162      */
163     private static final String PROJECT_BUILD_OUTPUTTIMESTAMP = "project.build.outputTimestamp";
164 
165     /**
166      * Classifier used in the name of the javadoc-options XML file, and in the resources bundle
167      * artifact that gets attached to the project. This one is used for non-test javadocs.
168      *
169      * @see #TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER
170      * @since 2.7
171      */
172     public static final String JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER = "javadoc-resources";
173 
174     /**
175      * Classifier used in the name of the javadoc-options XML file, and in the resources bundle
176      * artifact that gets attached to the project. This one is used for test-javadocs.
177      *
178      * @see #JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER
179      * @since 2.7
180      */
181     public static final String TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER = "test-javadoc-resources";
182 
183     /**
184      * The Javadoc script file name when <code>debug</code> parameter is on, i.e. javadoc.bat or javadoc.sh
185      */
186     protected static final String DEBUG_JAVADOC_SCRIPT_NAME = "javadoc." + ( SystemUtils.IS_OS_WINDOWS ? "bat" : "sh" );
187 
188     /**
189      * The <code>options</code> file name in the output directory when calling:
190      * <code>javadoc.exe(or .sh) &#x40;options &#x40;packages | &#x40;argfile | &#x40;files</code>
191      */
192     protected static final String OPTIONS_FILE_NAME = "options";
193 
194     /**
195      * The <code>packages</code> file name in the output directory when calling:
196      * <code>javadoc.exe(or .sh) &#x40;options &#x40;packages | &#x40;argfile | &#x40;files</code>
197      */
198     protected static final String PACKAGES_FILE_NAME = "packages";
199 
200     /**
201      * The <code>argfile</code> file name in the output directory when calling:
202      * <code>javadoc.exe(or .sh) &#x40;options &#x40;packages | &#x40;argfile | &#x40;files</code>
203      */
204     protected static final String ARGFILE_FILE_NAME = "argfile";
205 
206     /**
207      * The <code>files</code> file name in the output directory when calling:
208      * <code>javadoc.exe(or .sh) &#x40;options &#x40;packages | &#x40;argfile | &#x40;files</code>
209      */
210     protected static final String FILES_FILE_NAME = "files";
211 
212     /**
213      * The current class directory
214      */
215     private static final String RESOURCE_DIR = ClassUtils.getPackageName( JavadocReport.class ).replace( '.', '/' );
216 
217     /**
218      * Default css file name
219      */
220     private static final String DEFAULT_CSS_NAME = "stylesheet.css";
221 
222     /**
223      * Default location for css
224      */
225     private static final String RESOURCE_CSS_DIR = RESOURCE_DIR + "/css";
226 
227     private static final String PACKAGE_LIST = "package-list";
228     private static final String ELEMENT_LIST = "element-list";
229 
230 
231     /**
232      * For Javadoc options appears since Java 1.4.
233      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.1.html#summary">
234      * What's New in Javadoc 1.4</a>
235      *
236      * @since 2.1
237      */
238     private static final JavaVersion SINCE_JAVADOC_1_4 = JavaVersion.parse( "1.4" );
239 
240     /**
241      * For Javadoc options appears since Java 1.4.2.
242      * See <a
243      * href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.2.html#commandlineoptions">
244      * What's New in Javadoc 1.4.2</a>
245      *
246      * @since 2.1
247      */
248     private static final JavaVersion SINCE_JAVADOC_1_4_2 = JavaVersion.parse( "1.4.2" );
249 
250     /**
251      * For Javadoc options appears since Java 5.0.
252      * See <a
253      * href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.5.0.html#commandlineoptions">
254      * What's New in Javadoc 5.0</a>
255      *
256      * @since 2.1
257      */
258     private static final JavaVersion SINCE_JAVADOC_1_5 = JavaVersion.parse( "1.5" );
259 
260     /**
261      * For Javadoc options appears since Java 6.0.
262      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/index.html">
263      * Javadoc Technology</a>
264      *
265      * @since 2.4
266      */
267     private static final JavaVersion SINCE_JAVADOC_1_6 = JavaVersion.parse( "1.6" );
268 
269     /**
270      * For Javadoc options appears since Java 8.0.
271      * See <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/index.html">
272      * Javadoc Technology</a>
273      *
274      * @since 3.0.0
275      */
276     private static final JavaVersion SINCE_JAVADOC_1_8 = JavaVersion.parse( "1.8" );
277 
278     /**
279      *
280      */
281     private static final JavaVersion JAVA_VERSION = JavaVersion.JAVA_SPECIFICATION_VERSION;
282 
283     // ----------------------------------------------------------------------
284     // Mojo components
285     // ----------------------------------------------------------------------
286 
287     /**
288      * Archiver manager
289      *
290      * @since 2.5
291      */
292     @Component
293     private ArchiverManager archiverManager;
294 
295     @Component
296     private ResourceResolver resourceResolver;
297 
298     @Component
299     private ArtifactResolver artifactResolver;
300 
301     @Component
302     private ArtifactHandlerManager artifactHandlerManager;
303 
304     @Component
305     private DependencyResolver dependencyResolver;
306 
307     /**
308      * Project builder
309      *
310      * @since 3.0
311      */
312     @Component
313     private ProjectBuilder mavenProjectBuilder;
314 
315     /** */
316     @Component
317     private ToolchainManager toolchainManager;
318 
319     final LocationManager locationManager = new LocationManager();
320 
321     // ----------------------------------------------------------------------
322     // Mojo parameters
323     // ----------------------------------------------------------------------
324 
325     /**
326      * The current build session instance. This is used for
327      * toolchain manager API calls.
328      */
329     @Parameter( defaultValue = "${session}", readonly = true, required = true )
330     protected MavenSession session;
331 
332     /**
333      * The Maven Settings.
334      *
335      * @since 2.3
336      */
337     @Parameter( defaultValue = "${settings}", readonly = true, required = true )
338     private Settings settings;
339 
340     /**
341      * The Maven Project Object
342      */
343     @Parameter( defaultValue = "${project}", readonly = true, required = true )
344     protected MavenProject project;
345 
346     @Parameter( defaultValue = "${mojoExecution}", readonly = true )
347     private MojoExecution mojo;
348 
349     /**
350      * Specify if the Javadoc should operate in offline mode.
351      */
352     @Parameter( defaultValue = "${settings.offline}", required = true, readonly = true )
353     private boolean isOffline;
354 
355     /**
356      * Specifies the Javadoc resources directory to be included in the Javadoc (i.e. package.html, images...).
357      * <br/>
358      * Could be used in addition of <code>docfilessubdirs</code> parameter.
359      * <br/>
360      * See <a href="#docfilessubdirs">docfilessubdirs</a>.
361      *
362      * @see #docfilessubdirs
363      * @since 2.1
364      */
365     @Parameter( defaultValue = "${basedir}/src/main/javadoc" )
366     private File javadocDirectory;
367 
368     /**
369      * Set an additional option(s) on the command line. All input will be passed as-is to the
370      * {@code @options} file. You must take care of quoting and escaping. Useful for a custom doclet.
371      *
372      * @since 3.0.0
373      */
374     @Parameter
375     private String[] additionalOptions;
376 
377     /**
378      * Set an additional Javadoc option(s) (i.e. JVM options) on the command line.
379      * Example:
380      * <pre>
381      * &lt;additionalJOption&gt;-J-Xss128m&lt;/additionalJOption&gt;
382      * </pre>
383      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#J">Jflag</a>.
384      * <br/>
385      * See <a href="http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp">vmoptions</a>.
386      * <br/>
387      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/net/properties.html">Networking
388      * Properties</a>.
389      *
390      * @since 2.3
391      */
392     @Parameter( property = "additionalJOption" )
393     private String additionalJOption;
394 
395     /**
396      * Set additional JVM options for the execution of the javadoc command via the '-J' option to javadoc.
397      * Example:
398      * <pre>
399      *     &lt;additionalJOptions&gt;
400      *         &lt;additionalJOption&gt;-J-Xmx1g &lt;/additionalJOption&gt;
401      *     &lt;/additionalJOptions&gt;
402      * </pre>
403      * @since 2.9
404      */
405     @Parameter
406     private String[] additionalJOptions;
407 
408     /**
409      * A list of artifacts containing resources which should be copied into the
410      * Javadoc output directory (like stylesheets, icons, etc.).
411      * <br/>
412      * Example:
413      * <pre>
414      * &lt;resourcesArtifacts&gt;
415      *   &lt;resourcesArtifact&gt;
416      *     &lt;groupId&gt;external.group.id&lt;/groupId&gt;
417      *     &lt;artifactId&gt;external-resources&lt;/artifactId&gt;
418      *     &lt;version&gt;1.0&lt;/version&gt;
419      *   &lt;/resourcesArtifact&gt;
420      * &lt;/resourcesArtifacts&gt;
421      * </pre>
422      * <br/>
423      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/ResourcesArtifact.html">Javadoc</a>.
424      * <br/>
425      *
426      * @since 2.5
427      */
428     @Parameter( property = "resourcesArtifacts" )
429     private ResourcesArtifact[] resourcesArtifacts;
430 
431     /**
432      * The local repository where the artifacts are located.
433      */
434     @Parameter( property = "localRepository" )
435     private ArtifactRepository localRepository;
436 
437     /**
438      * The projects in the reactor for aggregation report.
439      */
440     @Parameter( property = "reactorProjects", readonly = true )
441     private List<MavenProject> reactorProjects;
442 
443     /**
444      * Set this to <code>true</code> to debug the Javadoc plugin. With this, <code>javadoc.bat(or.sh)</code>,
445      * <code>options</code>, <code>@packages</code> or <code>argfile</code> files are provided in the output directory.
446      * <br/>
447      *
448      * @since 2.1
449      */
450     @Parameter( property = "debug", defaultValue = "false" )
451     private boolean debug;
452 
453     /**
454      * Sets the absolute path of the Javadoc Tool executable to use. Since version 2.5, a mere directory specification
455      * is sufficient to have the plugin use "javadoc" or "javadoc.exe" respectively from this directory.
456      *
457      * @since 2.3
458      */
459     @Parameter( property = "javadocExecutable" )
460     private String javadocExecutable;
461 
462     /**
463      * Version of the Javadoc Tool executable to use, ex. "1.3", "1.5".
464      *
465      * @since 2.3
466      */
467     @Parameter( property = "javadocVersion" )
468     private String javadocVersion;
469 
470     /**
471      * Version of the Javadoc Tool executable to use.
472      */
473     private JavaVersion javadocRuntimeVersion;
474 
475     /**
476      * Specifies whether the Javadoc generation should be skipped.
477      *
478      * @since 2.5
479      */
480     @Parameter( property = "maven.javadoc.skip", defaultValue = "false" )
481     protected boolean skip;
482 
483     /**
484      * Specifies if the build will fail if there are errors during javadoc execution or not.
485      *
486      * @since 2.5
487      */
488     @Parameter( property = "maven.javadoc.failOnError", defaultValue = "true" )
489     protected boolean failOnError;
490 
491 
492     /**
493      * Specifies if the build will fail if there are warning during javadoc execution or not.
494      *
495      * @since 3.0.1
496      */
497     @Parameter( property = "maven.javadoc.failOnWarnings", defaultValue = "false" )
498     protected boolean failOnWarnings;
499 
500     /**
501      * Specifies to use the
502      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#standard">
503      * options provided by the Standard Doclet</a> for a custom doclet.
504      * <br>
505      * Example:
506      * <pre>
507      * &lt;docletArtifacts&gt;
508      *   &lt;docletArtifact&gt;
509      *     &lt;groupId&gt;com.sun.tools.doclets&lt;/groupId&gt;
510      *     &lt;artifactId&gt;doccheck&lt;/artifactId&gt;
511      *     &lt;version&gt;1.2b2&lt;/version&gt;
512      *   &lt;/docletArtifact&gt;
513      * &lt;/docletArtifacts&gt;
514      * &lt;useStandardDocletOptions&gt;true&lt;/useStandardDocletOptions&gt;
515      * </pre>
516      *
517      * @since 2.5
518      */
519     @Parameter( property = "useStandardDocletOptions", defaultValue = "true" )
520     protected boolean useStandardDocletOptions;
521 
522     /**
523      * Detect the Javadoc links for all dependencies defined in the project. The detection is based on the default
524      * Maven conventions, i.e.: <code>${project.url}/apidocs</code>.
525      * <br/>
526      * For instance, if the project has a dependency to
527      * <a href="http://commons.apache.org/lang/">Apache Commons Lang</a> i.e.:
528      * <pre>
529      * &lt;dependency&gt;
530      *   &lt;groupId&gt;commons-lang&lt;/groupId&gt;
531      *   &lt;artifactId&gt;commons-lang&lt;/artifactId&gt;
532      * &lt;/dependency&gt;
533      * </pre>
534      * The added Javadoc <code>-link</code> parameter will be <code>http://commons.apache.org/lang/apidocs</code>.
535      *
536      * @see #links
537      * @see #dependencyLinks
538      * @since 2.6
539      */
540     @Parameter( property = "detectLinks", defaultValue = "false" )
541     private boolean detectLinks;
542 
543     /**
544      * Detect the links for all modules defined in the project.
545      * <br/>
546      * If {@link #reactorProjects} is defined in a non-aggregator way, it generates default offline links
547      * between modules based on the defined project's urls. For instance, if a parent project has two projects
548      * <code>module1</code> and <code>module2</code>, the <code>-linkoffline</code> will be:
549      * <br/>
550      * The added Javadoc <code>-linkoffline</code> parameter for <b>module1</b> will be
551      * <code>/absolute/path/to/</code><b>module2</b><code>/target/site/apidocs</code>
552      * <br/>
553      * The added Javadoc <code>-linkoffline</code> parameter for <b>module2</b> will be
554      * <code>/absolute/path/to/</code><b>module1</b><code>/target/site/apidocs</code>
555      *
556      * @see #offlineLinks
557      * @since 2.6
558      */
559     @Parameter( property = "detectOfflineLinks", defaultValue = "true" )
560     private boolean detectOfflineLinks;
561 
562     /**
563      * Detect the Java API link for the current build, i.e. <code>https://docs.oracle.com/javase/1.4.2/docs/api/</code>
564      * for Java source 1.4.
565      * <br/>
566      * By default, the goal detects the Javadoc API link depending the value of the <code>source</code>
567      * parameter in the <code>org.apache.maven.plugins:maven-compiler-plugin</code>
568      * (defined in <code>${project.build.plugins}</code> or in <code>${project.build.pluginManagement}</code>),
569      * or try to compute it from the {@link #javadocExecutable} version.
570      * <br/>
571      * See
572      * <a href="./apidocs/org/apache/maven/plugins/javadoc/AbstractJavadocMojo.html#DEFAULT_JAVA_API_LINKS">Javadoc</a>
573      * for the default values.
574      * <br/>
575      *
576      * @see #links
577      * @see #javaApiLinks
578      * @since 2.6
579      */
580     @Parameter( property = "detectJavaApiLink", defaultValue = "true" )
581     private boolean detectJavaApiLink;
582 
583     /**
584      * Use this parameter <b>only</b> if if you want to override the default URLs.
585      *
586      * The key should match {@code api_x}, where {@code x} matches the Java version.
587      *
588      *  For example:
589      *  <dl>
590      *   <dt>api_1.5</dt>
591      *   <dd>https://docs.oracle.com/javase/1.5.0/docs/api/</dd>
592      *   <dt>api_1.8<dt>
593      *   <dd>https://docs.oracle.com/javase/8/docs/api/</dd>
594      *   <dt>api_9</dd>
595      *   <dd>https://docs.oracle.com/javase/9/docs/api/</dd>
596      * </dl>
597      * @since 2.6
598      */
599     @Parameter( property = "javaApiLinks" )
600     private Properties javaApiLinks;
601 
602     /**
603      * Flag controlling content validation of <code>package-list</code> resources. If set, the content of
604      * <code>package-list</code> resources will be validated.
605      *
606      * @since 2.8
607      */
608     @Parameter( property = "validateLinks", defaultValue = "false" )
609     private boolean validateLinks;
610 
611     // ----------------------------------------------------------------------
612     // Javadoc Options - all alphabetical
613     // ----------------------------------------------------------------------
614 
615     /**
616      * Specifies the paths where the boot classes reside. The <code>bootclasspath</code> can contain multiple paths
617      * by separating them with a colon (<code>:</code>) or a semi-colon (<code>;</code>).
618      * <br/>
619      * See <a href=
620      *    "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#bootclasspath">bootclasspath</a>.
621      *
622      * @since 2.5
623      */
624     @Parameter( property = "bootclasspath" )
625     private String bootclasspath;
626 
627     /**
628      * Specifies the artifacts where the boot classes reside.
629      * <br/>
630      * See <a href=
631      *   "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#bootclasspath">bootclasspath</a>.
632      * <br/>
633      * Example:
634      * <pre>
635      * &lt;bootclasspathArtifacts&gt;
636      *   &lt;bootclasspathArtifact&gt;
637      *     &lt;groupId&gt;my-groupId&lt;/groupId&gt;
638      *     &lt;artifactId&gt;my-artifactId&lt;/artifactId&gt;
639      *     &lt;version&gt;my-version&lt;/version&gt;
640      *   &lt;/bootclasspathArtifact&gt;
641      * &lt;/bootclasspathArtifacts&gt;
642      * </pre>
643      * <br/>
644      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/BootclasspathArtifact.html">Javadoc</a>.
645      * <br/>
646      *
647      * @since 2.5
648      */
649     @Parameter( property = "bootclasspathArtifacts" )
650     private BootclasspathArtifact[] bootclasspathArtifacts;
651 
652     /**
653      * Uses the sentence break iterator to determine the end of the first sentence.
654      * <br/>
655      * See <a href=
656      * "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#breakiterator">breakiterator</a>.
657      * <br/>
658      * Since <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java
659      * 1.4</a>.
660      * <br/>
661      */
662     @Parameter( property = "breakiterator", defaultValue = "false" )
663     private boolean breakiterator;
664 
665     /**
666      * Specifies the class file that starts the doclet used in generating the documentation.
667      * <br/>
668      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#doclet">doclet</a>.
669      */
670     @Parameter( property = "doclet" )
671     private String doclet;
672 
673     /**
674      * Specifies the artifact containing the doclet starting class file (specified with the <code>-doclet</code>
675      * option).
676      * <br/>
677      * See
678      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#docletpath">docletpath</a>.
679      * <br/>
680      * Example:
681      * <pre>
682      * &lt;docletArtifact&gt;
683      *   &lt;groupId&gt;com.sun.tools.doclets&lt;/groupId&gt;
684      *   &lt;artifactId&gt;doccheck&lt;/artifactId&gt;
685      *   &lt;version&gt;1.2b2&lt;/version&gt;
686      * &lt;/docletArtifact&gt;
687      * </pre>
688      * <br/>
689      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/DocletArtifact.html">Javadoc</a>.
690      * <br/>
691      */
692     @Parameter( property = "docletArtifact" )
693     private DocletArtifact docletArtifact;
694 
695     /**
696      * Specifies multiple artifacts containing the path for the doclet starting class file (specified with the
697      * <code>-doclet</code> option).
698      * <br/>
699      * See
700      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#docletpath">docletpath</a>.
701      * <br/>
702      * Example:
703      * <pre>
704      * &lt;docletArtifacts&gt;
705      *   &lt;docletArtifact&gt;
706      *     &lt;groupId&gt;com.sun.tools.doclets&lt;/groupId&gt;
707      *     &lt;artifactId&gt;doccheck&lt;/artifactId&gt;
708      *     &lt;version&gt;1.2b2&lt;/version&gt;
709      *   &lt;/docletArtifact&gt;
710      * &lt;/docletArtifacts&gt;
711      * </pre>
712      * <br/>
713      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/DocletArtifact.html">Javadoc</a>.
714      * <br/>
715      *
716      * @since 2.1
717      */
718     @Parameter( property = "docletArtifacts" )
719     private DocletArtifact[] docletArtifacts;
720 
721     /**
722      * Specifies the path to the doclet starting class file (specified with the <code>-doclet</code> option) and
723      * any jar files it depends on. The <code>docletPath</code> can contain multiple paths by separating them with
724      * a colon (<code>:</code>) or a semi-colon (<code>;</code>).
725      * <br/>
726      * See
727      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#docletpath">docletpath</a>.
728      */
729     @Parameter( property = "docletPath" )
730     private String docletPath;
731 
732     /**
733      * Specifies the encoding name of the source files. If not specificed, the encoding value will be the value of the
734      * <code>file.encoding</code> system property.
735      * <br/>
736      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#encoding">encoding</a>.
737      * <br/>
738      * <b>Note</b>: In 2.4, the default value was locked to <code>ISO-8859-1</code> to ensure reproducing build, but
739      * this was reverted in 2.5.
740      * <br/>
741      */
742     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
743     private String encoding;
744 
745     /**
746      * Unconditionally excludes the specified packages and their subpackages from the list formed by
747      * <code>-subpackages</code>. Multiple packages can be separated by commas (<code>,</code>), colons (<code>:</code>)
748      * or semicolons (<code>;</code>).
749      * <p>
750      * Wildcards work as followed:
751      * <ul>
752      *   <li>a wildcard at the beginning should match 1 or more folders</li>
753      *   <li>any other wildcard must match exactly one folder</li>
754      * </ul>
755      * </p>
756      * <p>
757      * Example:
758      * <pre>
759      * &lt;excludePackageNames&gt;*.internal:org.acme.exclude1.*:org.acme.exclude2&lt;/excludePackageNames&gt;
760      * </pre>
761      * <br/>
762      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#exclude">exclude</a>.
763      * <br/>
764      * Since <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java
765      * 1.4</a>.
766      * </p>
767      */
768     @Parameter( property = "excludePackageNames" )
769     private String excludePackageNames;
770 
771     /**
772      * Specifies the directories where extension classes reside. Separate directories in <code>extdirs</code> with a
773      * colon (<code>:</code>) or a semi-colon (<code>;</code>).
774      * <br/>
775      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#extdirs">extdirs</a>.
776      */
777     @Parameter( property = "extdirs" )
778     private String extdirs;
779 
780     /**
781      * Specifies the locale that javadoc uses when generating documentation.
782      * <br/>
783      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#locale">locale</a>.
784      */
785     @Parameter( property = "locale" )
786     private String locale;
787 
788     /**
789      * Specifies the maximum Java heap size to be used when launching the Javadoc tool.
790      * JVMs refer to this property as the <code>-Xmx</code> parameter. Example: '512' or '512m'.
791      * The memory unit depends on the JVM used. The units supported could be: <code>k</code>, <code>kb</code>,
792      * <code>m</code>, <code>mb</code>, <code>g</code>, <code>gb</code>, <code>t</code>, <code>tb</code>.
793      * If no unit specified, the default unit is <code>m</code>.
794      */
795     @Parameter( property = "maxmemory" )
796     private String maxmemory;
797 
798     /**
799      * Specifies the minimum Java heap size to be used when launching the Javadoc tool.
800      * JVMs refer to this property as the <code>-Xms</code> parameter. Example: '512' or '512m'.
801      * The memory unit depends on the JVM used. The units supported could be: <code>k</code>, <code>kb</code>,
802      * <code>m</code>, <code>mb</code>, <code>g</code>, <code>gb</code>, <code>t</code>, <code>tb</code>.
803      * If no unit specified, the default unit is <code>m</code>.
804      */
805     @Parameter( property = "minmemory" )
806     private String minmemory;
807 
808     /**
809      * This option creates documentation with the appearance and functionality of documentation generated by
810      * Javadoc 1.1.
811      * <br/>
812      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#1.1">1.1</a>.
813      * <br/>
814      */
815     @Parameter( property = "old", defaultValue = "false" )
816     private boolean old;
817 
818     /**
819      * Specifies that javadoc should retrieve the text for the overview documentation from the "source" file
820      * specified by path/filename and place it on the Overview page (overview-summary.html).
821      * <br/>
822      * <b>Note</b>: could be in conflict with &lt;nooverview/&gt;.
823      * <br/>
824      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#overview">overview</a>.
825      * <br/>
826      */
827     @Parameter( property = "overview", defaultValue = "${basedir}/src/main/javadoc/overview.html" )
828     private File overview;
829 
830     /**
831      * Shuts off non-error and non-warning messages, leaving only the warnings and errors appear, making them
832      * easier to view.
833      * <br/>
834      * Note: was a standard doclet in Java 1.4.2 (refer to bug ID
835      * <a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4714350">4714350</a>).
836      * <br/>
837      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#quiet">quiet</a>.
838      * <br/>
839      * Since Java 5.0.
840      * <br/>
841      */
842     @Parameter( property = "quiet", defaultValue = "false" )
843     private boolean quiet;
844 
845     /**
846      * Specifies the access level for classes and members to show in the Javadocs.
847      * Possible values are:
848      * <ul>
849      * <li><a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#public">public</a>
850      * (shows only public classes and members)</li>
851      * <li><a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#protected">protected</a>
852      * (shows only public and protected classes and members)</li>
853      * <li><a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#package">package</a>
854      * (shows all classes and members not marked private)</li>
855      * <li><a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#private">private</a>
856      * (shows all classes and members)</li>
857      * </ul>
858      * <br/>
859      */
860     @Parameter( property = "show", defaultValue = "protected" )
861     private String show;
862 
863     /**
864      * Necessary to enable javadoc to handle assertions introduced in J2SE v 1.4 source code or generics introduced in
865      * J2SE v5.
866      * <br/>
867      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#source">source</a>.
868      * <br/>
869      * Since <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java
870      * 1.4</a>.
871      */
872     @Parameter( property = "source", defaultValue = "${maven.compiler.source}" )
873     private String source;
874 
875     /**
876      * Provide source compatibility with specified release
877      *
878      * @since JDK 9
879      * @since 3.1.0
880      */
881     @Parameter( defaultValue = "${maven.compiler.release}" )
882     private String release;
883 
884     /**
885      * Specifies the source paths where the subpackages are located. The <code>sourcepath</code> can contain
886      * multiple paths by separating them with a colon (<code>:</code>) or a semi-colon (<code>;</code>).
887      * <br/>
888      * See
889      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#sourcepath">sourcepath</a>.
890      */
891     @Parameter( property = "sourcepath" )
892     private String sourcepath;
893 
894     /**
895      * Specifies the package directory where javadoc will be executed. Multiple packages can be separated by
896      * colons (<code>:</code>).
897      * <br/>
898      * See
899      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#subpackages">subpackages</a>.
900      * <br/>
901      * Since <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java
902      * 1.4</a>.
903      */
904     @Parameter( property = "subpackages" )
905     private String subpackages;
906 
907     /**
908      * Provides more detailed messages while javadoc is running.
909      * <br/>
910      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#verbose">verbose</a>.
911      * <br/>
912      */
913     @Parameter( property = "verbose", defaultValue = "false" )
914     private boolean verbose;
915 
916     // ----------------------------------------------------------------------
917     // Standard Doclet Options - all alphabetical
918     // ----------------------------------------------------------------------
919 
920     /**
921      * Specifies whether or not the author text is included in the generated Javadocs.
922      * <br/>
923      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#author">author</a>.
924      * <br/>
925      */
926     @Parameter( property = "author", defaultValue = "true" )
927     private boolean author;
928 
929     /**
930      * Specifies the text to be placed at the bottom of each output file.<br/>
931      * If you want to use html, you have to put it in a CDATA section, <br/>
932      * e.g. <code>&lt;![CDATA[Copyright 2005, &lt;a href="http://www.mycompany.com">MyCompany, Inc.&lt;a>]]&gt;</code>
933      * <br>
934      * <strong>Note:<strong>If the project has the property <code>project.build.outputTimestamp</code>, its year will
935      * be used as {currentYear}. This way it is possible to generate reproducible javadoc jars.
936      * <br>
937      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#bottom">bottom</a>.
938      * <br>
939      */
940     @Parameter( property = "bottom",
941                     defaultValue = "Copyright &#169; {inceptionYear}&#x2013;{currentYear} {organizationName}. "
942                         + "All rights reserved." )
943     private String bottom;
944 
945     /**
946      * Specifies the HTML character set for this document. If not specificed, the charset value will be the value of
947      * the <code>docencoding</code> parameter.
948      * <br/>
949      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#charset">charset</a>.
950      * <br/>
951      */
952     @Parameter( property = "charset" )
953     private String charset;
954 
955     /**
956      * Specifies the encoding of the generated HTML files. If not specificed, the docencoding value will be
957      * <code>UTF-8</code>.
958      * <br/>
959      * See
960      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#docencoding">docencoding</a>.
961      */
962     @Parameter( property = "docencoding", defaultValue = "${project.reporting.outputEncoding}" )
963     private String docencoding;
964 
965     /**
966      * Enables deep copying of the <code>&#42;&#42;/doc-files</code> directories and the specifc <code>resources</code>
967      * directory from the <code>javadocDirectory</code> directory (for instance,
968      * <code>src/main/javadoc/com/mycompany/myapp/doc-files</code> and <code>src/main/javadoc/resources</code>).
969      * <br/>
970      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#docfilessubdirs">
971      * docfilessubdirs</a>.
972      * <br/>
973      * Since <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java
974      * 1.4</a>.
975      * <br/>
976      * See <a href="#javadocDirectory">javadocDirectory</a>.
977      * <br/>
978      *
979      * @see #excludedocfilessubdir
980      * @see #javadocDirectory
981      */
982     @Parameter( property = "docfilessubdirs", defaultValue = "false" )
983     private boolean docfilessubdirs;
984 
985     /**
986      * Specifies specific checks to be performed on Javadoc comments.
987      * <br/>
988      * See <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#BEJEFABE">doclint</a>.
989      *
990      * @since 3.0.0
991      */
992     @Parameter( property = "doclint" )
993     private String doclint;
994 
995     /**
996      * Specifies the title to be placed near the top of the overview summary file.
997      * <br/>
998      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#doctitle">doctitle</a>.
999      * <br/>
1000      */
1001     @Parameter( property = "doctitle", defaultValue = "${project.name} ${project.version} API" )
1002     private String doctitle;
1003 
1004     /**
1005      * Excludes any "doc-files" subdirectories with the given names. Multiple patterns can be excluded
1006      * by separating them with colons (<code>:</code>).
1007      * <br/>
1008      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#excludedocfilessubdir">
1009      * excludedocfilessubdir</a>.
1010      * <br/>
1011      * Since <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java
1012      * 1.4</a>.
1013      *
1014      * @see #docfilessubdirs
1015      */
1016     @Parameter( property = "excludedocfilessubdir" )
1017     private String excludedocfilessubdir;
1018 
1019     /**
1020      * Specifies the footer text to be placed at the bottom of each output file.
1021      * <br/>
1022      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#footer">footer</a>.
1023      */
1024     @Parameter( property = "footer" )
1025     private String footer;
1026 
1027     /**
1028      * Separates packages on the overview page into whatever groups you specify, one group per table. The
1029      * packages pattern can be any package name, or can be the start of any package name followed by an asterisk
1030      * (<code>*</code>) meaning "match any characters". Multiple patterns can be included in a group
1031      * by separating them with colons (<code>:</code>).
1032      * <br/>
1033      * Example:
1034      * <pre>
1035      * &lt;groups&gt;
1036      *   &lt;group&gt;
1037      *     &lt;title&gt;Core Packages&lt;/title&gt;
1038      *     &lt;!-- To includes java.lang, java.lang.ref,
1039      *     java.lang.reflect and only java.util
1040      *     (i.e. not java.util.jar) --&gt;
1041      *     &lt;packages&gt;java.lang*:java.util&lt;/packages&gt;
1042      *   &lt;/group&gt;
1043      *   &lt;group&gt;
1044      *     &lt;title&gt;Extension Packages&lt;/title&gt;
1045      *     &nbsp;&lt;!-- To include javax.accessibility,
1046      *     javax.crypto, ... (among others) --&gt;
1047      *     &lt;packages&gt;javax.*&lt;/packages&gt;
1048      *   &lt;/group&gt;
1049      * &lt;/groups&gt;
1050      * </pre>
1051      * <b>Note</b>: using <code>java.lang.*</code> for <code>packages</code> would omit the <code>java.lang</code>
1052      * package but using <code>java.lang*</code> will include it.
1053      * <br/>
1054      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#group">group</a>.
1055      * <br/>
1056      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/Group.html">Javadoc</a>.
1057      * <br/>
1058      */
1059     @Parameter
1060     private Group[] groups;
1061 
1062     /**
1063      * Specifies the header text to be placed at the top of each output file.
1064      * <br/>
1065      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#header">header</a>.
1066      */
1067     @Parameter( property = "header" )
1068     private String header;
1069 
1070     /**
1071      * Specifies the path of an alternate help file path\filename that the HELP link in the top and bottom
1072      * navigation bars link to.
1073      * <br/>
1074      * <b>Note</b>: could be in conflict with &lt;nohelp/&gt;.
1075      * <br/>
1076      * The <code>helpfile</code> could be an absolute File path.
1077      * <br/>
1078      * Since 2.6, it could be also be a path from a resource in the current project source directories
1079      * (i.e. <code>src/main/java</code>, <code>src/main/resources</code> or <code>src/main/javadoc</code>)
1080      * or from a resource in the Javadoc plugin dependencies, for instance:
1081      * <pre>
1082      * &lt;helpfile&gt;path/to/your/resource/yourhelp-doc.html&lt;/helpfile&gt;
1083      * </pre>
1084      * Where <code>path/to/your/resource/yourhelp-doc.html</code> could be in <code>src/main/javadoc</code>.
1085      * <pre>
1086      * &lt;build&gt;
1087      *   &lt;plugins&gt;
1088      *     &lt;plugin&gt;
1089      *       &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
1090      *       &lt;artifactId&gt;maven-javadoc-plugin&lt;/artifactId&gt;
1091      *       &lt;configuration&gt;
1092      *         &lt;helpfile&gt;path/to/your/resource/yourhelp-doc.html&lt;/helpfile&gt;
1093      *         ...
1094      *       &lt;/configuration&gt;
1095      *       &lt;dependencies&gt;
1096      *         &lt;dependency&gt;
1097      *           &lt;groupId&gt;groupId&lt;/groupId&gt;
1098      *           &lt;artifactId&gt;artifactId&lt;/artifactId&gt;
1099      *           &lt;version&gt;version&lt;/version&gt;
1100      *         &lt;/dependency&gt;
1101      *       &lt;/dependencies&gt;
1102      *     &lt;/plugin&gt;
1103      *     ...
1104      *   &lt;plugins&gt;
1105      * &lt;/build&gt;
1106      * </pre>
1107      * Where <code>path/to/your/resource/yourhelp-doc.html</code> is defined in the
1108      * <code>groupId:artifactId:version</code> javadoc plugin dependency.
1109      * <br/>
1110      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#helpfile">helpfile</a>.
1111      */
1112     @Parameter( property = "helpfile" )
1113     private String helpfile;
1114 
1115     /**
1116      * Adds HTML meta keyword tags to the generated file for each class.
1117      * <br/>
1118      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#keywords">keywords</a>.
1119      * <br/>
1120      * Since
1121      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.2.html#commandlineoptions">
1122      * Java 1.4.2</a>.
1123      * <br/>
1124      * Since
1125      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.5.0.html#commandlineoptions">
1126      * Java 5.0</a>.
1127      * <br/>
1128      *
1129      * @since 2.1
1130      */
1131     @Parameter( property = "keywords", defaultValue = "false" )
1132     private boolean keywords;
1133 
1134     /**
1135      * Creates links to existing javadoc-generated documentation of external referenced classes.
1136      * <br>
1137      * <b>Notes</b>:
1138      * <ol>
1139      * <li>only used if {@link #isOffline} is set to <code>false</code>.</li>
1140      * <li>all given links should have a fetchable <code>/package-list</code> file. For instance:
1141      * <pre>
1142      * &lt;links&gt;
1143      *   &lt;link&gt;https://docs.oracle.com/javase/1.4.2/docs/api&lt;/link&gt;
1144      * &lt;links&gt;
1145      * </pre>
1146      * will be used because <code>https://docs.oracle.com/javase/1.4.2/docs/api/package-list</code> exists.</li>
1147      * <li>if {@link #detectLinks} is defined, the links between the project dependencies are
1148      * automatically added.</li>
1149      * <li>if {@link #detectJavaApiLink} is defined, a Java API link, based on the Java version of the
1150      * project's sources, will be added automatically.</li>
1151      * </ol>
1152      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#link">link</a>.
1153      *
1154      * @see #detectLinks
1155      * @see #detectJavaApiLink
1156      * @see #dependencyLinks
1157      */
1158     @Parameter( property = "links" )
1159     protected ArrayList<String> links;
1160     
1161 
1162     /**
1163      * Redefine the apidoc URL for specific dependencies when using {@link #detectLinks}.
1164      * Useful if the dependency wasn't build with Maven or when the apidocs have been moved.
1165      * <pre>
1166      * &lt;dependencyLinks&gt;
1167      *   &lt;dependencyLink&gt;
1168      *     &lt;groupId&gt;groupId&lt;/groupId&gt;
1169      *     &lt;artifactId&gt;artifactId&lt;/artifactId&gt;
1170      *     &lt;classifier&gt;classifier&lt;/classifier&gt; &lt;!-- optional --&gt; 
1171      *     &lt;url&gt;version&lt;/url&gt;
1172      *   &lt;/dependencyLink&gt;
1173      * &lt;/dependencyLinks&gt;
1174      * </pre>
1175      * 
1176      * @see #detectLinks
1177      * @since 3.3.0
1178      */
1179     @Parameter
1180     private List<DependencyLink> dependencyLinks = new ArrayList<>();
1181 
1182     /**
1183      * Creates an HTML version of each source file (with line numbers) and adds links to them from the standard
1184      * HTML documentation.
1185      * <br/>
1186      * See
1187      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#linksource">linksource</a>.
1188      * <br/>
1189      * Since <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java
1190      * 1.4</a>.
1191      * <br/>
1192      */
1193     @Parameter( property = "linksource", defaultValue = "false" )
1194     private boolean linksource;
1195 
1196     /**
1197      * Suppress the entire comment body, including the main description and all tags, generating only declarations.
1198      * <br/>
1199      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#nocomment">nocomment</a>.
1200      * <br/>
1201      * Since <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java
1202      * 1.4</a>.
1203      * <br/>
1204      */
1205     @Parameter( property = "nocomment", defaultValue = "false" )
1206     private boolean nocomment;
1207 
1208     /**
1209      * Prevents the generation of any deprecated API at all in the documentation.
1210      * <br/>
1211      * See
1212      * <a
1213      * href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#nodeprecated">nodeprecated</a>.
1214      * <br/>
1215      */
1216     @Parameter( property = "nodeprecated", defaultValue = "false" )
1217     private boolean nodeprecated;
1218 
1219     /**
1220      * Prevents the generation of the file containing the list of deprecated APIs (deprecated-list.html) and the
1221      * link in the navigation bar to that page.
1222      * <br/>
1223      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#nodeprecatedlist">
1224      * nodeprecatedlist</a>.
1225      * <br/>
1226      */
1227     @Parameter( property = "nodeprecatedlist", defaultValue = "false" )
1228     private boolean nodeprecatedlist;
1229 
1230     /**
1231      * Omits the HELP link in the navigation bars at the top and bottom of each page of output.
1232      * <br/>
1233      * <b>Note</b>: could be in conflict with &lt;helpfile/&gt;.
1234      * <br/>
1235      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#nohelp">nohelp</a>.
1236      * <br/>
1237      */
1238     @Parameter( property = "nohelp", defaultValue = "false" )
1239     private boolean nohelp;
1240 
1241     /**
1242      * Omits the index from the generated docs.
1243      * <br/>
1244      * <b>Note</b>: could be in conflict with &lt;splitindex/&gt;.
1245      * <br/>
1246      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#noindex">noindex</a>.
1247      * <br/>
1248      */
1249     @Parameter( property = "noindex", defaultValue = "false" )
1250     private boolean noindex;
1251 
1252     /**
1253      * Omits the navigation bar from the generated docs.
1254      * <br/>
1255      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#nonavbar">nonavbar</a>.
1256      * <br/>
1257      */
1258     @Parameter( property = "nonavbar", defaultValue = "false" )
1259     private boolean nonavbar;
1260 
1261     /**
1262      * Omits the entire overview page from the generated docs.
1263      * <br/>
1264      * <b>Note</b>: could be in conflict with &lt;overview/&gt;.
1265      * <br/>
1266      * Standard Doclet undocumented option.
1267      * <br/>
1268      *
1269      * @since 2.4
1270      */
1271     @Parameter( property = "nooverview", defaultValue = "false" )
1272     private boolean nooverview;
1273 
1274     /**
1275      * Omits qualifying package name from ahead of class names in output.
1276      * Example:
1277      * <pre>
1278      * &lt;noqualifier&gt;all&lt;/noqualifier&gt;
1279      * or
1280      * &lt;noqualifier&gt;packagename1:packagename2&lt;/noqualifier&gt;
1281      * </pre>
1282      * See
1283      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#noqualifier">noqualifier</a>.
1284      * <br/>
1285      * Since <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java
1286      * 1.4</a>.
1287      */
1288     @Parameter( property = "noqualifier" )
1289     private String noqualifier;
1290 
1291     /**
1292      * Omits from the generated docs the "Since" sections associated with the since tags.
1293      * <br/>
1294      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#nosince">nosince</a>.
1295      * <br/>
1296      */
1297     @Parameter( property = "nosince", defaultValue = "false" )
1298     private boolean nosince;
1299 
1300     /**
1301      * Suppresses the timestamp, which is hidden in an HTML comment in the generated HTML near the top of each page.
1302      * <br/>
1303      * See
1304      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#notimestamp">notimestamp</a>.
1305      * <br/>
1306      * Since
1307      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.5.0.html#commandlineoptions">
1308      * Java 5.0</a>.
1309      * <br/>
1310      *
1311      * @since 2.1
1312      */
1313     @Parameter( property = "notimestamp", defaultValue = "false" )
1314     private boolean notimestamp;
1315 
1316     /**
1317      * Omits the class/interface hierarchy pages from the generated docs.
1318      * <br>
1319      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#notree">notree</a> option
1320      */
1321     @Parameter( property = "notree", defaultValue = "false" )
1322     private boolean notree;
1323 
1324     /**
1325      * This option is a variation of <code>-link</code>; they both create links to javadoc-generated documentation
1326      * for external referenced classes.
1327      * <br/>
1328      * See
1329      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#linkoffline">linkoffline</a>.
1330      * <br/>
1331      * Example:
1332      * <pre>
1333      * &lt;offlineLinks&gt;
1334      *   &lt;offlineLink&gt;
1335      *     &lt;url&gt;https://docs.oracle.com/javase/1.5.0/docs/api/&lt;/url&gt;
1336      *     &lt;location&gt;../javadoc/jdk-5.0/&lt;/location&gt;
1337      *   &lt;/offlineLink&gt;
1338      * &lt;/offlineLinks&gt;
1339      * </pre>
1340      * <br/>
1341      * <b>Note</b>: if {@link #detectOfflineLinks} is defined, the offline links between the project modules are
1342      * automatically added if the goal is calling in a non-aggregator way.
1343      * <br>
1344      * @see <a href="./apidocs/org/apache/maven/plugins/javadoc/options/OfflineLink.html">Javadoc</a>.
1345      */
1346     @Parameter( property = "offlineLinks" )
1347     private OfflineLink[] offlineLinks;
1348 
1349     /**
1350      * Specifies the destination directory where javadoc saves the generated HTML files.
1351      * <br>
1352      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#d">javadoc d</a>
1353      */
1354     @Parameter( property = "destDir", alias = "destDir", defaultValue = "${project.build.directory}/apidocs",
1355                     required = true )
1356     protected File outputDirectory;
1357 
1358     /**
1359      * Specify the text for upper left frame.
1360      * <br/>
1361      * Since
1362      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.2.html#commandlineoptions">
1363      * Java 1.4.2</a>.
1364      *
1365      * @since 2.1
1366      */
1367     @Parameter( property = "packagesheader" )
1368     private String packagesheader;
1369 
1370     /**
1371      * Generates compile-time warnings for missing serial tags.
1372      * <br/>
1373      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#serialwarn">serialwarn</a> option
1374      */
1375     @Parameter( property = "serialwarn", defaultValue = "false" )
1376     private boolean serialwarn;
1377 
1378     /**
1379      * Specify the number of spaces each tab takes up in the source. If no tab is used in source, the default
1380      * space is used.
1381      * <br/>
1382      * Note: was <code>linksourcetab</code> in Java 1.4.2 (refer to bug ID
1383      * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4788919">4788919</a>).
1384      * <br/>
1385      * Since
1386      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.2.html#commandlineoptions">
1387      * 1.4.2</a>.
1388      * <br/>
1389      * Since Java 5.0.
1390      *
1391      * @since 2.1
1392      */
1393     @Parameter( property = "sourcetab", alias = "linksourcetab" )
1394     private int sourcetab;
1395 
1396     /**
1397      * Splits the index file into multiple files, alphabetically, one file per letter, plus a file for any index
1398      * entries that start with non-alphabetical characters.
1399      * <br/>
1400      * <b>Note</b>: could be in conflict with &lt;noindex/&gt;.
1401      * <br/>
1402      * See
1403      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#splitindex">splitindex</a>.
1404      * <br/>
1405      */
1406     @Parameter( property = "splitindex", defaultValue = "false" )
1407     private boolean splitindex;
1408 
1409     /**
1410      * Specifies whether the stylesheet to be used is the <code>maven</code>'s javadoc stylesheet or
1411      * <code>java</code>'s default stylesheet when a <i>stylesheetfile</i> parameter is not specified.
1412      * <br/>
1413      * Possible values: <code>maven<code> or <code>java</code>.
1414      * <br/>
1415      */
1416     @Parameter( property = "stylesheet", defaultValue = "java" )
1417     private String stylesheet;
1418 
1419     /**
1420      * Specifies the path of an alternate HTML stylesheet file.
1421      * <br/>
1422      * The <code>stylesheetfile</code> could be an absolute File path.
1423      * <br/>
1424      * Since 2.6, it could be also be a path from a resource in the current project source directories
1425      * (i.e. <code>src/main/java</code>, <code>src/main/resources</code> or <code>src/main/javadoc</code>)
1426      * or from a resource in the Javadoc plugin dependencies, for instance:
1427      * <pre>
1428      * &lt;stylesheetfile&gt;path/to/your/resource/yourstylesheet.css&lt;/stylesheetfile&gt;
1429      * </pre>
1430      * Where <code>path/to/your/resource/yourstylesheet.css</code> could be in <code>src/main/javadoc</code>.
1431      * <pre>
1432      * &lt;build&gt;
1433      *   &lt;plugins&gt;
1434      *     &lt;plugin&gt;
1435      *       &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
1436      *       &lt;artifactId&gt;maven-javadoc-plugin&lt;/artifactId&gt;
1437      *       &lt;configuration&gt;
1438      *         &lt;stylesheetfile&gt;path/to/your/resource/yourstylesheet.css&lt;/stylesheetfile&gt;
1439      *         ...
1440      *       &lt;/configuration&gt;
1441      *       &lt;dependencies&gt;
1442      *         &lt;dependency&gt;
1443      *           &lt;groupId&gt;groupId&lt;/groupId&gt;
1444      *           &lt;artifactId&gt;artifactId&lt;/artifactId&gt;
1445      *           &lt;version&gt;version&lt;/version&gt;
1446      *         &lt;/dependency&gt;
1447      *       &lt;/dependencies&gt;
1448      *     &lt;/plugin&gt;
1449      *     ...
1450      *   &lt;plugins&gt;
1451      * &lt;/build&gt;
1452      * </pre>
1453      * Where <code>path/to/your/resource/yourstylesheet.css</code> is defined in the
1454      * <code>groupId:artifactId:version</code> javadoc plugin dependency.
1455      * <br/>
1456      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#stylesheetfile">
1457      * stylesheetfile</a>.
1458      */
1459     @Parameter( property = "stylesheetfile" )
1460     private String stylesheetfile;
1461     
1462     /**
1463      * Specifies the path of an additional HTML stylesheet file relative to the {@link #javadocDirectory}
1464      * Example:
1465      * <pre>
1466      *     &lt;addStylesheets&gt;
1467      *         &lt;resources/addstylesheet.css&lt;/addStylesheet&gt;
1468      *     &lt;/addStylesheets&gt;
1469      * </pre>
1470      * @since 3.3.0
1471      */
1472     @Parameter
1473     private String[] addStylesheets;
1474     
1475     /**
1476      * Specifies the class file that starts the taglet used in generating the documentation for that tag.
1477      * <br/>
1478      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#taglet">taglet</a>.
1479      * <br/>
1480      * Since
1481      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java 1.4</a>.
1482      */
1483     @Parameter( property = "taglet" )
1484     private String taglet;
1485 
1486     /**
1487      * Specifies the Taglet artifact containing the taglet class files (.class).
1488      * <br/>
1489      * See
1490      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#tagletpath">tagletpath</a>.
1491      * <br/>
1492      * Example:
1493      * <pre>
1494      * &lt;taglets&gt;
1495      *   &lt;taglet&gt;
1496      *     &lt;tagletClass&gt;com.sun.tools.doclets.ToDoTaglet&lt;/tagletClass&gt;
1497      *   &lt;/taglet&gt;
1498      *   &lt;taglet&gt;
1499      *     &lt;tagletClass&gt;package.to.AnotherTagletClass&lt;/tagletClass&gt;
1500      *   &lt;/taglet&gt;
1501      *   ...
1502      * &lt;/taglets&gt;
1503      * &lt;tagletArtifact&gt;
1504      *   &lt;groupId&gt;group-Taglet&lt;/groupId&gt;
1505      *   &lt;artifactId&gt;artifact-Taglet&lt;/artifactId&gt;
1506      *   &lt;version&gt;version-Taglet&lt;/version&gt;
1507      * &lt;/tagletArtifact&gt;
1508      * </pre>
1509      * <br/>
1510      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/TagletArtifact.html">Javadoc</a>.
1511      * <br/>
1512      *
1513      * @since 2.1
1514      */
1515     @Parameter( property = "tagletArtifact" )
1516     private TagletArtifact tagletArtifact;
1517 
1518     /**
1519      * Specifies several Taglet artifacts containing the taglet class files (.class). These taglets class names will be
1520      * auto-detect and so no need to specify them.
1521      * <br/>
1522      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#taglet">taglet</a>.
1523      * <br/>
1524      * See
1525      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#tagletpath">tagletpath</a>.
1526      * <br/>
1527      * Example:
1528      * <pre>
1529      * &lt;tagletArtifacts&gt;
1530      *   &lt;tagletArtifact&gt;
1531      *     &lt;groupId&gt;group-Taglet&lt;/groupId&gt;
1532      *     &lt;artifactId&gt;artifact-Taglet&lt;/artifactId&gt;
1533      *     &lt;version&gt;version-Taglet&lt;/version&gt;
1534      *   &lt;/tagletArtifact&gt;
1535      *   ...
1536      * &lt;/tagletArtifacts&gt;
1537      * </pre>
1538      * <br/>
1539      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/TagletArtifact.html">Javadoc</a>.
1540      * <br/>
1541      *
1542      * @since 2.5
1543      */
1544     @Parameter( property = "tagletArtifacts" )
1545     private TagletArtifact[] tagletArtifacts;
1546 
1547     /**
1548      * Specifies the search paths for finding taglet class files (.class). The <code>tagletpath</code> can contain
1549      * multiple paths by separating them with a colon (<code>:</code>) or a semi-colon (<code>;</code>).
1550      * <br/>
1551      * See
1552      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#tagletpath">tagletpath</a>.
1553      * <br/>
1554      * Since
1555      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java 1.4</a>.
1556      */
1557     @Parameter( property = "tagletpath" )
1558     private String tagletpath;
1559 
1560     /**
1561      * Enables the Javadoc tool to interpret multiple taglets.
1562      * <br/>
1563      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#taglet">taglet</a>.
1564      * <br/>
1565      * See
1566      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#tagletpath">tagletpath</a>.
1567      * <br/>
1568      * Example:
1569      * <pre>
1570      * &lt;taglets&gt;
1571      *   &lt;taglet&gt;
1572      *     &lt;tagletClass&gt;com.sun.tools.doclets.ToDoTaglet&lt;/tagletClass&gt;
1573      *     &lt;!--&lt;tagletpath&gt;/home/taglets&lt;/tagletpath&gt;--&gt;
1574      *     &lt;tagletArtifact&gt;
1575      *       &lt;groupId&gt;group-Taglet&lt;/groupId&gt;
1576      *       &lt;artifactId&gt;artifact-Taglet&lt;/artifactId&gt;
1577      *       &lt;version&gt;version-Taglet&lt;/version&gt;
1578      *     &lt;/tagletArtifact&gt;
1579      *   &lt;/taglet&gt;
1580      * &lt;/taglets&gt;
1581      * </pre>
1582      * <br/>
1583      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/Taglet.html">Javadoc</a>.
1584      * <br/>
1585      *
1586      * @since 2.1
1587      */
1588     @Parameter( property = "taglets" )
1589     private Taglet[] taglets;
1590 
1591     /**
1592      * Enables the Javadoc tool to interpret a simple, one-argument custom block tag tagname in doc comments.
1593      * <br/>
1594      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#tag">tag</a>.
1595      * <br/>
1596      * Since
1597      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java 1.4</a>.
1598      * <br/>
1599      * Example:
1600      * <pre>
1601      * &lt;tags&gt;
1602      *   &lt;tag&gt;
1603      *     &lt;name&gt;todo&lt;/name&gt;
1604      *     &lt;placement&gt;a&lt;/placement&gt;
1605      *     &lt;head&gt;To Do:&lt;/head&gt;
1606      *   &lt;/tag&gt;
1607      * &lt;/tags&gt;
1608      * </pre>
1609      * <b>Note</b>: the placement should be a combinaison of Xaoptcmf letters:
1610      * <ul>
1611      * <li><b><code>X</code></b> (disable tag)</li>
1612      * <li><b><code>a</code></b> (all)</li>
1613      * <li><b><code>o</code></b> (overview)</li>
1614      * <li><b><code>p</code></b> (packages)</li>
1615      * <li><b><code>t</code></b> (types, that is classes and interfaces)</li>
1616      * <li><b><code>c</code></b> (constructors)</li>
1617      * <li><b><code>m</code></b> (methods)</li>
1618      * <li><b><code>f</code></b> (fields)</li>
1619      * </ul>
1620      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/Tag.html">Javadoc</a>.
1621      * <br/>
1622      */
1623     @Parameter( property = "tags" )
1624     private Tag[] tags;
1625 
1626     /**
1627      * Specifies the top text to be placed at the top of each output file.
1628      * <br/>
1629      * See <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6227616">6227616</a>.
1630      * <br/>
1631      * Since Java 6.0
1632      *
1633      * @since 2.4
1634      */
1635     @Parameter( property = "top" )
1636     private String top;
1637 
1638     /**
1639      * Includes one "Use" page for each documented class and package.
1640      * <br/>
1641      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#use">use</a>.
1642      * <br/>
1643      */
1644     @Parameter( property = "use", defaultValue = "true" )
1645     private boolean use;
1646 
1647     /**
1648      * Includes the version text in the generated docs.
1649      * <br/>
1650      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#version">version</a>.
1651      * <br/>
1652      */
1653     @Parameter( property = "version", defaultValue = "true" )
1654     private boolean version;
1655 
1656     /**
1657      * Specifies the title to be placed in the HTML title tag.
1658      * <br/>
1659      * See
1660      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#windowtitle">windowtitle</a>.
1661      * <br/>
1662      */
1663     @Parameter( property = "windowtitle", defaultValue = "${project.name} ${project.version} API" )
1664     private String windowtitle;
1665 
1666     /**
1667      * Whether dependency -sources jars should be resolved and included as source paths for javadoc generation.
1668      * This is useful when creating javadocs for a distribution project.
1669      *
1670      * @since 2.7
1671      */
1672     @Parameter( defaultValue = "false" )
1673     private boolean includeDependencySources;
1674 
1675     /**
1676      * Directory where unpacked project sources / test-sources should be cached.
1677      *
1678      * @see #includeDependencySources
1679      * @since 2.7
1680      */
1681     @Parameter( defaultValue = "${project.build.directory}/distro-javadoc-sources" )
1682     private File sourceDependencyCacheDir;
1683 
1684     /**
1685      * Whether to include transitive dependencies in the list of dependency -sources jars to include in this javadoc
1686      * run.
1687      *
1688      * @see #includeDependencySources
1689      * @since 2.7
1690      * @deprecated if these sources depend on transitive dependencies, those dependencies should be added to the pom as
1691      *             direct dependencies
1692      */
1693     @Parameter( defaultValue = "false" )
1694     @Deprecated
1695     private boolean includeTransitiveDependencySources;
1696 
1697     /**
1698      * List of included dependency-source patterns. Example: <code>org.apache.maven:*</code>
1699      *
1700      * @see #includeDependencySources
1701      * @since 2.7
1702      */
1703     @Parameter
1704     private List<String> dependencySourceIncludes;
1705 
1706     /**
1707      * List of excluded dependency-source patterns. Example: <code>org.apache.maven.shared:*</code>
1708      *
1709      * @see #includeDependencySources
1710      * @since 2.7
1711      */
1712     @Parameter
1713     private List<String> dependencySourceExcludes;
1714 
1715     /**
1716      * Directory into which assembled {@link JavadocOptions} instances will be written before they
1717      * are added to javadoc resources bundles.
1718      *
1719      * @since 2.7
1720      */
1721     @Parameter( defaultValue = "${project.build.directory}/javadoc-bundle-options", readonly = true )
1722     private File javadocOptionsDir;
1723 
1724     /**
1725      * Transient variable to allow lazy-resolution of javadoc bundles from dependencies, so they can
1726      * be used at various points in the javadoc generation process.
1727      *
1728      * @since 2.7
1729      */
1730     private transient List<JavadocBundle> dependencyJavadocBundles;
1731 
1732     /**
1733      * Capability to add additional dependencies to the javadoc classpath.
1734      * Example:
1735      * <pre>
1736      * &lt;additionalDependencies&gt;
1737      *   &lt;additionalDependency&gt;
1738      *     &lt;groupId&gt;geronimo-spec&lt;/groupId&gt;
1739      *     &lt;artifactId&gt;geronimo-spec-jta&lt;/artifactId&gt;
1740      *     &lt;version&gt;1.0.1B-rc4&lt;/version&gt;
1741      *   &lt;/additionalDependency&gt;
1742      * &lt;/additionalDependencies&gt;
1743      * </pre>
1744      *
1745      * @since 2.8.1
1746      */
1747     @Parameter
1748     private List<AdditionalDependency> additionalDependencies;
1749 
1750     /**
1751      * Include filters on the source files. Default is **\/\*.java.
1752      * These are ignored if you specify subpackages or subpackage excludes.
1753      *
1754      * @since 2.9
1755      */
1756     @Parameter
1757     private List<String> sourceFileIncludes;
1758 
1759     /**
1760      * exclude filters on the source files.
1761      * These are ignored if you specify subpackages or subpackage excludes.
1762      *
1763      * @since 2.9
1764      */
1765     @Parameter
1766     private List<String> sourceFileExcludes;
1767 
1768     /**
1769      * To apply the security fix on generated javadoc see http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-1571
1770      * @since 2.9.1
1771      */
1772     @Parameter( defaultValue = "true", property = "maven.javadoc.applyJavadocSecurityFix" )
1773     private boolean applyJavadocSecurityFix = true;
1774 
1775     /**
1776      * <p>
1777      * Allow for configuration of the javadoc tool via maven toolchains.
1778      * This overrules the toolchain selected by the maven-toolchain-plugin.
1779      * </p>
1780      *
1781      * <p>Examples:</p>
1782      * (see <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html">
1783      *     Guide to Toolchains</a> for more info)
1784      *
1785      * <pre>
1786      * {@code
1787      *    <configuration>
1788      *        ...
1789      *        <jdkToolchain>
1790      *            <version>11</version>
1791      *        </jdkToolchain>
1792      *    </configuration>
1793      *
1794      *    <configuration>
1795      *        ...
1796      *        <jdkToolchain>
1797      *            <version>1.8</version>
1798      *            <vendor>zulu</vendor>
1799      *        </jdkToolchain>
1800      *    </configuration>
1801      *    }
1802      * </pre>
1803      * 
1804      * <strong>note:</strong> requires at least Maven 3.3.1
1805      *
1806      * @since 3.0.0
1807      */
1808     @Parameter
1809     private Map<String, String> jdkToolchain;
1810 
1811     /**
1812      * <p>
1813      * Location of the file used to store the state of the previous javadoc run.
1814      * This is used to skip the generation if nothing has changed.
1815      * </p>
1816      *
1817      * @since 3.2.0
1818      */
1819     @Parameter( property = "staleDataPath",
1820             defaultValue = "${project.build.directory}/maven-javadoc-plugin-stale-data.txt" )
1821     private File staleDataPath;
1822 
1823 
1824     /**
1825      * <p>
1826      * Comma separated list of modules (artifactId) to not add in aggregated javadoc
1827      * </p>
1828      *
1829      * @since 3.2.0
1830      */
1831     @Parameter( property = "maven.javadoc.skippedModules" )
1832     private String skippedModules;
1833 
1834     // ----------------------------------------------------------------------
1835     // protected methods
1836     // ----------------------------------------------------------------------
1837 
1838     /**
1839      * Indicates whether this goal is flagged with <code>@aggregator</code>.
1840      *
1841      * @return <code>true</code> if the goal is designed as an aggregator, <code>false</code> otherwise.
1842      * @see AggregatorJavadocReport
1843      * @see AggregatorTestJavadocReport
1844      */
1845     protected boolean isAggregator()
1846     {
1847         return false;
1848     }
1849 
1850     /**
1851      * Indicates whether this goal generates documentation for the <code>Java Test code</code>.
1852      *
1853      * @return <code>true</code> if the goal generates Test Javadocs, <code>false</code> otherwise.
1854      */
1855     protected boolean isTest()
1856     {
1857         return false;
1858     }
1859 
1860     /**
1861      * @return the output directory
1862      */
1863     protected String getOutputDirectory()
1864     {
1865         return outputDirectory.getAbsoluteFile().toString();
1866     }
1867 
1868     protected MavenProject getProject()
1869     {
1870         return project;
1871     }
1872 
1873     /**
1874      * @param p not null maven project
1875      * @return the list of directories where compiled classes are placed for the given project. These dirs are
1876      *         added in the javadoc classpath.
1877      */
1878     protected List<File> getProjectBuildOutputDirs( MavenProject p )
1879     {
1880         if ( StringUtils.isEmpty( p.getBuild().getOutputDirectory() ) )
1881         {
1882             return Collections.emptyList();
1883         }
1884 
1885         return Collections.singletonList( new File( p.getBuild().getOutputDirectory() ) );
1886     }
1887 
1888     protected File getArtifactFile( MavenProject project )
1889     {
1890         if ( !isAggregator() && isTest() )
1891         {
1892             return null;
1893         }
1894         else if ( project.getArtifact() != null && project.getArtifact().getFile() != null )
1895         {
1896             return project.getArtifact().getFile();
1897         }
1898         else if ( project.getExecutionProject() != null && project.getExecutionProject().getArtifact() != null ) 
1899         {
1900             return project.getExecutionProject().getArtifact().getFile();
1901         }
1902         return null;
1903     }
1904 
1905     /**
1906      * @param p not null maven project
1907      * @return the list of source paths for the given project
1908      */
1909     protected List<String> getProjectSourceRoots( MavenProject p )
1910     {
1911         if ( "pom".equals( p.getPackaging().toLowerCase() ) )
1912         {
1913             return Collections.emptyList();
1914         }
1915 
1916         return ( p.getCompileSourceRoots() == null
1917             ? Collections.<String>emptyList()
1918             : new LinkedList<>( p.getCompileSourceRoots() ) );
1919     }
1920 
1921     /**
1922      * @param p not null maven project
1923      * @return the list of source paths for the execution project of the given project
1924      */
1925     protected List<String> getExecutionProjectSourceRoots( MavenProject p )
1926     {
1927         if ( "pom".equals( p.getExecutionProject().getPackaging().toLowerCase() ) )
1928         {
1929             return Collections.emptyList();
1930         }
1931 
1932         return ( p.getExecutionProject().getCompileSourceRoots() == null
1933             ? Collections.<String>emptyList()
1934             : new LinkedList<>( p.getExecutionProject().getCompileSourceRoots() ) );
1935     }
1936 
1937     /**
1938      * @return the current javadoc directory
1939      */
1940     protected File getJavadocDirectory()
1941     {
1942         return javadocDirectory;
1943     }
1944 
1945     /**
1946      * @return the doclint specific checks configuration
1947      */
1948     protected String getDoclint()
1949     {
1950         return doclint;
1951     }
1952 
1953     /**
1954      * @return the title to be placed near the top of the overview summary file
1955      */
1956     protected String getDoctitle()
1957     {
1958         return doctitle;
1959     }
1960 
1961     /**
1962      * @return the overview documentation file from the user parameter or from the <code>javadocdirectory</code>
1963      */
1964     protected File getOverview()
1965     {
1966         return overview;
1967     }
1968 
1969     /**
1970      * @return the title to be placed in the HTML title tag
1971      */
1972     protected String getWindowtitle()
1973     {
1974         return windowtitle;
1975     }
1976 
1977     /**
1978      * @return the charset attribute or the value of {@link #getDocencoding()} if <code>null</code>.
1979      */
1980     private String getCharset()
1981     {
1982         return ( StringUtils.isEmpty( charset ) ) ? getDocencoding() : charset;
1983     }
1984 
1985     /**
1986      * @return the docencoding attribute or <code>UTF-8</code> if <code>null</code>.
1987      */
1988     private String getDocencoding()
1989     {
1990         return ( StringUtils.isEmpty( docencoding ) ) ? ReaderFactory.UTF_8 : docencoding;
1991     }
1992 
1993     /**
1994      * @return the encoding attribute or the value of <code>file.encoding</code> system property if <code>null</code>.
1995      */
1996     private String getEncoding()
1997     {
1998         return ( StringUtils.isEmpty( encoding ) ) ? ReaderFactory.FILE_ENCODING : encoding;
1999     }
2000 
2001     @Override
2002     public void execute()
2003         throws MojoExecutionException, MojoFailureException
2004     {
2005         verifyRemovedParameter( "aggregator" );
2006         verifyRemovedParameter( "proxyHost" );
2007         verifyRemovedParameter( "proxyPort" );
2008         verifyReplacedParameter( "additionalparam", "additionalOptions" );
2009 
2010         doExecute();
2011     }
2012 
2013     abstract void doExecute() throws MojoExecutionException, MojoFailureException;
2014 
2015     protected final void verifyRemovedParameter( String paramName )
2016     {
2017         Xpp3Dom configDom = mojo.getConfiguration();
2018         if ( configDom != null )
2019         {
2020             if ( configDom.getChild( paramName ) != null )
2021             {
2022                 throw new IllegalArgumentException( "parameter '" + paramName
2023                     + "' has been removed from the plugin, please verify documentation." );
2024             }
2025         }
2026     }
2027 
2028     private void verifyReplacedParameter( String oldParamName, String newParamNew )
2029     {
2030         Xpp3Dom configDom = mojo.getConfiguration();
2031         if ( configDom != null )
2032         {
2033             if ( configDom.getChild( oldParamName ) != null )
2034             {
2035                 throw new IllegalArgumentException( "parameter '" + oldParamName
2036                     + "' has been replaced with " + newParamNew + ", please verify documentation." );
2037             }
2038         }
2039     }
2040 
2041     /**
2042      * The <a href="package-summary.html">package documentation</a> details the
2043      * Javadoc Options used by this Plugin.
2044      *
2045      * @param unusedLocale the wanted locale (actually unused).
2046      * @throws MavenReportException if any
2047      */
2048     protected void executeReport( Locale unusedLocale )
2049         throws MavenReportException
2050     {
2051         if ( skip )
2052         {
2053             getLog().info( "Skipping javadoc generation" );
2054             return;
2055         }
2056 
2057         if ( getLog().isDebugEnabled() )
2058         {
2059             this.debug = true;
2060         }
2061 
2062         // NOTE: Always generate this file, to allow javadocs from modules to be aggregated via
2063         // useDependencySources in a distro module build.
2064         try
2065         {
2066             buildJavadocOptions();
2067         }
2068         catch ( IOException e )
2069         {
2070             throw new MavenReportException( "Failed to generate javadoc options file: " + e.getMessage(), e );
2071         }
2072 
2073         Collection<JavadocModule> sourcePaths = getSourcePaths();
2074 
2075         Collection<Path> collectedSourcePaths = sourcePaths.stream()
2076                                                            .flatMap( e -> e.getSourcePaths().stream() )
2077                                                            .collect( Collectors.toList() );
2078 
2079         Map<Path, Collection<String>> files = getFiles( collectedSourcePaths );
2080         if ( !canGenerateReport( files ) )
2081         {
2082             return;
2083         }
2084 
2085         // ----------------------------------------------------------------------
2086         // Find the javadoc executable and version
2087         // ----------------------------------------------------------------------
2088 
2089         String jExecutable;
2090         try
2091         {
2092             jExecutable = getJavadocExecutable();
2093         }
2094         catch ( IOException e )
2095         {
2096             throw new MavenReportException( "Unable to find javadoc command: " + e.getMessage(), e );
2097         }
2098         setFJavadocVersion( new File( jExecutable ) );
2099 
2100         Collection<String> packageNames;
2101         if ( javadocRuntimeVersion.isAtLeast( "9" ) )
2102         {
2103             packageNames = getPackageNamesRespectingJavaModules( sourcePaths );
2104         }
2105         else
2106         {
2107             packageNames = getPackageNames( files );
2108         }
2109 
2110         // ----------------------------------------------------------------------
2111         // Javadoc output directory as File
2112         // ----------------------------------------------------------------------
2113 
2114         File javadocOutputDirectory = new File( getOutputDirectory() );
2115         if ( javadocOutputDirectory.exists() && !javadocOutputDirectory.isDirectory() )
2116         {
2117             throw new MavenReportException( "IOException: " + getOutputDirectory() + " is not a directory." );
2118         }
2119         if ( javadocOutputDirectory.exists() && !javadocOutputDirectory.canWrite() )
2120         {
2121             throw new MavenReportException( "IOException: " + getOutputDirectory() + " is not writable." );
2122         }
2123         javadocOutputDirectory.mkdirs();
2124 
2125         // ----------------------------------------------------------------------
2126         // Copy all resources
2127         // ----------------------------------------------------------------------
2128 
2129         copyAllResources( javadocOutputDirectory );
2130 
2131         // ----------------------------------------------------------------------
2132         // Create command line for Javadoc
2133         // ----------------------------------------------------------------------
2134 
2135         Commandline cmd = new Commandline();
2136         cmd.getShell().setQuotedArgumentsEnabled( false ); // for Javadoc JVM args
2137         cmd.setWorkingDirectory( javadocOutputDirectory.getAbsolutePath() );
2138         cmd.setExecutable( jExecutable );
2139 
2140         // ----------------------------------------------------------------------
2141         // Wrap Javadoc JVM args
2142         // ----------------------------------------------------------------------
2143 
2144         addMemoryArg( cmd, "-Xmx", this.maxmemory );
2145         addMemoryArg( cmd, "-Xms", this.minmemory );
2146         addProxyArg( cmd );
2147 
2148         if ( StringUtils.isNotEmpty( additionalJOption ) )
2149         {
2150             cmd.createArg().setValue( additionalJOption );
2151         }
2152 
2153         if ( additionalJOptions != null && additionalJOptions.length != 0 )
2154         {
2155             for ( String jo : additionalJOptions )
2156             {
2157                 cmd.createArg().setValue( jo );
2158             }
2159         }
2160 
2161         // ----------------------------------------------------------------------
2162         // Wrap Standard doclet Options
2163         // ----------------------------------------------------------------------
2164         List<String> standardDocletArguments = new ArrayList<>();
2165 
2166         Set<OfflineLink> offlineLinks;
2167         if ( StringUtils.isEmpty( doclet ) || useStandardDocletOptions )
2168         {
2169             offlineLinks = getLinkofflines();
2170             addStandardDocletOptions( javadocOutputDirectory, standardDocletArguments, offlineLinks );
2171         }
2172         else
2173         {
2174             offlineLinks = Collections.emptySet();
2175         }
2176 
2177         // ----------------------------------------------------------------------
2178         // Wrap Javadoc options
2179         // ----------------------------------------------------------------------
2180         List<String> javadocArguments = new ArrayList<>();
2181 
2182         addJavadocOptions( javadocOutputDirectory, javadocArguments, sourcePaths, offlineLinks );
2183 
2184         // ----------------------------------------------------------------------
2185         // Write options file and include it in the command line
2186         // ----------------------------------------------------------------------
2187 
2188         List<String> arguments = new ArrayList<>( javadocArguments.size() + standardDocletArguments.size() );
2189         arguments.addAll( javadocArguments );
2190         arguments.addAll( standardDocletArguments );
2191 
2192         if ( arguments.size() > 0 )
2193         {
2194             addCommandLineOptions( cmd, arguments, javadocOutputDirectory );
2195         }
2196 
2197         // ----------------------------------------------------------------------
2198         // Write packages file and include it in the command line
2199         // ----------------------------------------------------------------------
2200 
2201         // MJAVADOC-365 if includes/excludes are specified, these take precedence over the default
2202         // package-based mode and force javadoc into file-based mode unless subpackages are
2203         // specified. Subpackages take precedence over file-based include/excludes. Why? Because
2204         // getFiles(...) returns an empty list when subpackages are specified.
2205         boolean includesExcludesActive =
2206             ( sourceFileIncludes != null && !sourceFileIncludes.isEmpty() )
2207                 || ( sourceFileExcludes != null && !sourceFileExcludes.isEmpty() );
2208         if ( includesExcludesActive && !StringUtils.isEmpty( subpackages ) )
2209         {
2210             getLog().warn( "sourceFileIncludes and sourceFileExcludes have no effect when subpackages are specified!" );
2211             includesExcludesActive = false;
2212         }
2213         if ( !packageNames.isEmpty() && !includesExcludesActive )
2214         {
2215             addCommandLinePackages( cmd, javadocOutputDirectory, packageNames );
2216 
2217             // ----------------------------------------------------------------------
2218             // Write argfile file and include it in the command line
2219             // ----------------------------------------------------------------------
2220 
2221             List<String> specialFiles = getSpecialFiles( files );
2222 
2223             if ( !specialFiles.isEmpty() )
2224             {
2225                 addCommandLineArgFile( cmd, javadocOutputDirectory, specialFiles );
2226             }
2227         }
2228         else
2229         {
2230             // ----------------------------------------------------------------------
2231             // Write argfile file and include it in the command line
2232             // ----------------------------------------------------------------------
2233 
2234             List<String> allFiles = new ArrayList<>();
2235             for ( Map.Entry<Path, Collection<String>> filesEntry : files.entrySet() )
2236             {
2237                 for ( String file : filesEntry.getValue() )
2238                 {
2239                     allFiles.add( filesEntry.getKey().resolve( file ).toString() );
2240                 }
2241             }
2242 
2243             if ( !files.isEmpty() )
2244             {
2245                 addCommandLineArgFile( cmd, javadocOutputDirectory, allFiles );
2246             }
2247         }
2248 
2249         // ----------------------------------------------------------------------
2250         // Execute command line
2251         // ----------------------------------------------------------------------
2252 
2253         executeJavadocCommandLine( cmd, javadocOutputDirectory );
2254 
2255         // delete generated javadoc files only if no error and no debug mode
2256         // [MJAVADOC-336] Use File.delete() instead of File.deleteOnExit() to
2257         // prevent these files from making their way into archives.
2258         if ( !debug )
2259         {
2260             for ( int i = 0; i < cmd.getArguments().length; i++ )
2261             {
2262                 String arg = cmd.getArguments()[i].trim();
2263 
2264                 if ( !arg.startsWith( "@" ) )
2265                 {
2266                     continue;
2267                 }
2268 
2269                 File argFile = new File( javadocOutputDirectory, arg.substring( 1 ) );
2270                 if ( argFile.exists() )
2271                 {
2272                     argFile.delete();
2273                 }
2274             }
2275 
2276             File scriptFile = new File( javadocOutputDirectory, DEBUG_JAVADOC_SCRIPT_NAME );
2277             if ( scriptFile.exists() )
2278             {
2279                 scriptFile.delete();
2280             }
2281         }
2282         if ( applyJavadocSecurityFix )
2283         {
2284             // finally, patch the Javadoc vulnerability in older Javadoc tools (CVE-2013-1571):
2285             try
2286             {
2287                 final int patched = fixFrameInjectionBug( javadocOutputDirectory, getDocencoding() );
2288                 if ( patched > 0 )
2289                 {
2290                     getLog().info(
2291                         String.format( "Fixed Javadoc frame injection vulnerability (CVE-2013-1571) in %d files.",
2292                                        patched ) );
2293                 }
2294             }
2295             catch ( IOException e )
2296             {
2297                 throw new MavenReportException( "Failed to patch javadocs vulnerability: " + e.getMessage(), e );
2298             }
2299         }
2300         else
2301         {
2302           getLog().info( "applying javadoc security fix has been disabled" );
2303         }
2304     }
2305 
2306     /**
2307      * Method to get the files on the specified source paths
2308      *
2309      * @param sourcePaths a Collection that contains the paths to the source files
2310      * @return a List that contains the specific path for every source file
2311      * @throws MavenReportException {@link MavenReportException} issue while generating report
2312      */
2313     protected Map<Path, Collection<String>> getFiles( Collection<Path> sourcePaths )
2314         throws MavenReportException
2315     {
2316         Map<Path, Collection<String>> mappedFiles = new LinkedHashMap<>( sourcePaths.size() );
2317         if ( StringUtils.isEmpty( subpackages ) )
2318         {
2319             Collection<String> excludedPackages = getExcludedPackages();
2320             
2321             final boolean autoExclude;
2322             if ( release != null )
2323             {
2324                 autoExclude = JavaVersion.parse( release ).isBefore( "9" );
2325             }
2326             else if ( source != null )
2327             {
2328                 autoExclude = JavaVersion.parse( source ).isBefore( "9" );
2329             }
2330             else
2331             {
2332                 autoExclude = false;
2333             }
2334 
2335             for ( Path sourcePath : sourcePaths )
2336             {
2337                 File sourceDirectory = sourcePath.toFile();
2338                 List<String> files = new ArrayList<>( JavadocUtil.getFilesFromSource( sourceDirectory,
2339                         sourceFileIncludes, sourceFileExcludes,
2340                         excludedPackages ) );
2341 
2342                 if ( autoExclude && files.remove( "module-info.java" ) )
2343                 {
2344                     getLog().debug( "Auto exclude module-info.java due to source value" );
2345                 }
2346                 mappedFiles.put( sourcePath, files );
2347             }
2348         }
2349 
2350         return mappedFiles;
2351     }
2352 
2353     /**
2354      * Method to get the source paths per reactorProject. If no source path is specified in the parameter, the compile
2355      * source roots of the project will be used.
2356      *
2357      * @return a Map of the project absolute source paths per projects key (G:A)
2358      * @throws MavenReportException {@link MavenReportException} issue while generating report
2359      * @see JavadocUtil#pruneDirs(MavenProject, Collection)
2360      */
2361     protected Collection<JavadocModule> getSourcePaths()
2362         throws MavenReportException
2363     {
2364         Collection<JavadocModule> mappedSourcePaths = new ArrayList<>();
2365 
2366         if ( StringUtils.isEmpty( sourcepath ) )
2367         {
2368             if ( !"pom".equals( project.getPackaging()  ) )
2369             {
2370                 Set<Path> sourcePaths =
2371                     new LinkedHashSet<>( JavadocUtil.pruneDirs( project, getProjectSourceRoots( project ) ) );
2372 
2373                 if ( project.getExecutionProject() != null )
2374                 {
2375                     sourcePaths.addAll( JavadocUtil.pruneDirs( project, getExecutionProjectSourceRoots( project ) ) );
2376                 }
2377 
2378                 /*
2379                  * Should be after the source path (i.e. -sourcepath '.../src/main/java;.../src/main/javadoc') and *not*
2380                  * the opposite. If not, the javadoc tool always copies doc files, even if -docfilessubdirs is not
2381                  * setted.
2382                  */
2383                 if ( getJavadocDirectory() != null )
2384                 {
2385                     File javadocDir = getJavadocDirectory();
2386                     if ( javadocDir.exists() && javadocDir.isDirectory() )
2387                     {
2388                         Collection<Path> l =
2389                             JavadocUtil.pruneDirs( project,
2390                                                Collections.singletonList( getJavadocDirectory().getAbsolutePath() ) );
2391                         sourcePaths.addAll( l );
2392                     }
2393                 }
2394                 if ( !sourcePaths.isEmpty() )
2395                 {
2396                     mappedSourcePaths.add( new JavadocModule( ArtifactUtils.versionlessKey( project.getGroupId(),
2397                                                                                             project.getArtifactId() ),
2398                                                               getArtifactFile( project ),
2399                                                               sourcePaths ) );
2400                 }
2401             }
2402 
2403             if ( isAggregator() )
2404             {
2405                 for ( MavenProject subProject : getAggregatedProjects() )
2406                 {
2407                     if ( subProject != project )
2408                     {
2409                         Collection<Path> additionalSourcePaths = new ArrayList<>();
2410 
2411                         List<String> sourceRoots = getProjectSourceRoots( subProject );
2412 
2413                         if ( subProject.getExecutionProject() != null )
2414                         {
2415                             sourceRoots.addAll( getExecutionProjectSourceRoots( subProject ) );
2416                         }
2417 
2418                         ArtifactHandler artifactHandler = subProject.getArtifact().getArtifactHandler();
2419                         if ( "java".equals( artifactHandler.getLanguage() ) )
2420                         {
2421                             additionalSourcePaths.addAll( JavadocUtil.pruneDirs( subProject, sourceRoots ) );
2422                         }
2423 
2424                         if ( getJavadocDirectory() != null )
2425                         {
2426                             String javadocDirRelative =
2427                                 PathUtils.toRelative( project.getBasedir(),
2428                                                       getJavadocDirectory().getAbsolutePath() );
2429                             File javadocDir = new File( subProject.getBasedir(), javadocDirRelative );
2430                             if ( javadocDir.exists() && javadocDir.isDirectory() )
2431                             {
2432                                 Collection<Path> l = JavadocUtil.pruneDirs( subProject, Collections.singletonList(
2433                                         javadocDir.getAbsolutePath() ) );
2434                                 additionalSourcePaths.addAll( l );
2435                             }
2436                         }
2437                         
2438                         if ( !additionalSourcePaths.isEmpty() )
2439                         {
2440                             mappedSourcePaths.add( new JavadocModule( 
2441                                                           ArtifactUtils.versionlessKey( subProject.getGroupId(),
2442                                                                                         subProject.getArtifactId() ),
2443                                                           getArtifactFile( subProject ),
2444                                                           additionalSourcePaths ) );
2445                         }
2446                     }
2447                 }
2448             }
2449 
2450             if ( includeDependencySources )
2451             {
2452                 mappedSourcePaths.addAll( getDependencySourcePaths() );
2453             }
2454         }
2455         else
2456         {
2457             Collection<Path> sourcePaths =
2458                 JavadocUtil.pruneDirs( project,
2459                                        new ArrayList<>( Arrays.asList( JavadocUtil.splitPath( sourcepath ) ) ) );
2460             if ( getJavadocDirectory() != null )
2461             {
2462                 Collection<Path> l = JavadocUtil.pruneDirs( project, Collections.singletonList(
2463                     getJavadocDirectory().getAbsolutePath() ) );
2464                 sourcePaths.addAll( l );
2465             }
2466             
2467             if ( !sourcePaths.isEmpty() )
2468             {
2469                 mappedSourcePaths.add( new JavadocModule( ArtifactUtils.versionlessKey( project.getGroupId(),
2470                                                                                         project.getArtifactId() ),
2471                                                           getArtifactFile( project ),
2472                                                           sourcePaths ) );
2473             }
2474         }
2475 
2476         return mappedSourcePaths;
2477     }
2478 
2479     /**
2480      * Recursively add the modules of the aggregatedProject to the set of aggregatedModules.
2481      *
2482      * @param aggregatedProject the project being aggregated
2483      * @param reactorProjectsMap map of (still) available reactor projects
2484      */
2485     private Set<MavenProject> modulesForAggregatedProject( MavenProject aggregatedProject,
2486                                                            Map<Path, MavenProject> reactorProjectsMap )
2487     {
2488         // Maven does not supply an easy way to get the projects representing
2489         // the modules of a project. So we will get the paths to the base
2490         // directories of the modules from the project and compare with the
2491         // base directories of the projects in the reactor.
2492 
2493         if ( aggregatedProject.getModules().isEmpty() )
2494         {
2495             return Collections.singleton( aggregatedProject );
2496         }
2497 
2498         Path basePath = aggregatedProject.getBasedir().toPath();
2499         List<Path> modulePaths = new LinkedList<>();
2500         for ( String module :  aggregatedProject.getModules() )
2501         {
2502             modulePaths.add( basePath.resolve( module ).normalize() );
2503         }
2504 
2505         Set<MavenProject> aggregatedModules = new LinkedHashSet<>();
2506 
2507         for ( Path modulePath : modulePaths )
2508         {
2509             MavenProject module = reactorProjectsMap.remove( modulePath );
2510             if ( module != null )
2511             {
2512                 aggregatedModules.addAll( modulesForAggregatedProject( module, reactorProjectsMap ) );
2513             }
2514         }
2515 
2516         return aggregatedModules;
2517     }
2518 
2519     /**
2520      * Override this method to customize the configuration for resolving dependency sources. The default
2521      * behavior enables the resolution of -sources jar files.
2522      * @param config {@link SourceResolverConfig}
2523      * @return {@link SourceResolverConfig}
2524      */
2525     protected SourceResolverConfig configureDependencySourceResolution( final SourceResolverConfig config )
2526     {
2527         return config.withCompileSources();
2528     }
2529 
2530     /**
2531      * Resolve dependency sources so they can be included directly in the javadoc process. To customize this,
2532      * override {@link AbstractJavadocMojo#configureDependencySourceResolution(SourceResolverConfig)}.
2533      * @return List of source paths.
2534      * @throws MavenReportException {@link MavenReportException}
2535      */
2536     protected final Collection<JavadocModule> getDependencySourcePaths()
2537         throws MavenReportException
2538     {
2539         try
2540         {
2541             if ( sourceDependencyCacheDir.exists() )
2542             {
2543                 FileUtils.forceDelete( sourceDependencyCacheDir );
2544                 sourceDependencyCacheDir.mkdirs();
2545             }
2546         }
2547         catch ( IOException e )
2548         {
2549             throw new MavenReportException(
2550                 "Failed to delete cache directory: " + sourceDependencyCacheDir + "\nReason: " + e.getMessage(), e );
2551         }
2552 
2553         final SourceResolverConfig config = getDependencySourceResolverConfig();
2554 
2555         try
2556         {
2557             return resourceResolver.resolveDependencySourcePaths( config );
2558         }
2559         catch ( final ArtifactResolutionException | ArtifactNotFoundException e )
2560         {
2561             throw new MavenReportException(
2562                 "Failed to resolve one or more javadoc source/resource artifacts:\n\n" + e.getMessage(), e );
2563         }
2564     }
2565 
2566     /**
2567      * Returns a ArtifactFilter that only includes direct dependencies of this project
2568      * (verified via groupId and artifactId).
2569      *
2570      * @return
2571      */
2572     private TransformableFilter createDependencyArtifactFilter()
2573     {
2574         Set<Artifact> dependencyArtifacts = project.getDependencyArtifacts();
2575 
2576         List<String> artifactPatterns = new ArrayList<>( dependencyArtifacts.size() );
2577         for ( Artifact artifact : dependencyArtifacts )
2578         {
2579             artifactPatterns.add( artifact.getGroupId() + ":" + artifact.getArtifactId() );
2580         }
2581 
2582         return new PatternInclusionsFilter( artifactPatterns );
2583     }
2584 
2585     /**
2586      * Construct a SourceResolverConfig for resolving dependency sources and resources in a consistent
2587      * way, so it can be reused for both source and resource resolution.
2588      *
2589      * @since 2.7
2590      */
2591     private SourceResolverConfig getDependencySourceResolverConfig()
2592     {
2593         final List<TransformableFilter> andFilters = new ArrayList<>();
2594 
2595         final List<String> dependencyIncludes = dependencySourceIncludes;
2596         final List<String> dependencyExcludes = dependencySourceExcludes;
2597 
2598         if ( !includeTransitiveDependencySources || isNotEmpty( dependencyIncludes ) || isNotEmpty(
2599             dependencyExcludes ) )
2600         {
2601             if ( !includeTransitiveDependencySources )
2602             {
2603                 andFilters.add( createDependencyArtifactFilter() );
2604             }
2605 
2606             if ( isNotEmpty( dependencyIncludes ) )
2607             {
2608                 andFilters.add( new PatternInclusionsFilter( dependencyIncludes ) );
2609             }
2610 
2611             if ( isNotEmpty( dependencyExcludes ) )
2612             {
2613                 andFilters.add( new PatternExclusionsFilter( dependencyExcludes ) );
2614             }
2615         }
2616         
2617         return configureDependencySourceResolution( new SourceResolverConfig( project,
2618                                                   getProjectBuildingRequest( project ),
2619                                                   sourceDependencyCacheDir )
2620                                                   .withReactorProjects( this.reactorProjects ) )
2621                                                   .withFilter( new AndFilter( andFilters ) );
2622         
2623     }
2624     
2625     private ProjectBuildingRequest getProjectBuildingRequest( MavenProject currentProject )
2626     {
2627         return new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() )
2628                           .setRemoteRepositories( currentProject.getRemoteArtifactRepositories() );
2629     }
2630 
2631     /**
2632      * Method that indicates whether the javadoc can be generated or not. If the project does not contain any source
2633      * files and no subpackages are specified, the plugin will terminate.
2634      *
2635      * @param files the project files
2636      * @return a boolean that indicates whether javadoc report can be generated or not
2637      */
2638     protected boolean canGenerateReport( Map<Path, Collection<String>> files )
2639     {
2640         for ( Collection<String> filesValues : files.values() )
2641         {
2642             if ( !filesValues.isEmpty() )
2643             {
2644                 return true;
2645             }
2646         }
2647 
2648         return !StringUtils.isEmpty( subpackages );
2649     }
2650 
2651     // ----------------------------------------------------------------------
2652     // private methods
2653     // ----------------------------------------------------------------------
2654 
2655     /**
2656      * Method to get the excluded source files from the javadoc and create the argument string
2657      * that will be included in the javadoc commandline execution.
2658      *
2659      * @param sourcePaths the collection of paths to the source files
2660      * @return a String that contains the exclude argument that will be used by javadoc
2661      * @throws MavenReportException
2662      */
2663     private String getExcludedPackages( Collection<Path> sourcePaths )
2664         throws MavenReportException
2665     {
2666         List<String> excludedNames = null;
2667 
2668         if ( StringUtils.isNotEmpty( sourcepath ) && StringUtils.isNotEmpty( subpackages ) )
2669         {
2670             Collection<String> excludedPackages = getExcludedPackages();
2671 
2672             excludedNames = JavadocUtil.getExcludedPackages( sourcePaths, excludedPackages );
2673         }
2674 
2675         String excludeArg = "";
2676         if ( StringUtils.isNotEmpty( subpackages ) && excludedNames != null )
2677         {
2678             // add the excludedpackage names
2679             excludeArg = StringUtils.join( excludedNames.iterator(), ":" );
2680         }
2681 
2682         return excludeArg;
2683     }
2684 
2685     /**
2686      * Method to format the specified source paths that will be accepted by the javadoc tool.
2687      *
2688      * @param sourcePaths the list of paths to the source files that will be included in the javadoc.
2689      * @return a String that contains the formatted source path argument, separated by the System pathSeparator
2690      *         string (colon (<code>:</code>) on Solaris or semi-colon (<code>;</code>) on Windows).
2691      * @see File#pathSeparator
2692      */
2693     private String getSourcePath( Collection<Path> sourcePaths )
2694     {
2695         String sourcePath = null;
2696 
2697         if ( StringUtils.isEmpty( subpackages ) || StringUtils.isNotEmpty( sourcepath ) )
2698         {
2699             sourcePath = StringUtils.join( sourcePaths.iterator(), File.pathSeparator );
2700         }
2701 
2702         return sourcePath;
2703     }
2704 
2705     /**
2706      * Method to get the packages specified in the <code>excludePackageNames</code> parameter. The packages are split
2707      * with ',', ':', or ';' and then formatted.
2708      *
2709      * @return an array of String objects that contain the package names
2710      * @throws MavenReportException
2711      */
2712     private Collection<String> getExcludedPackages()
2713         throws MavenReportException
2714     {
2715         Set<String> excluded = new LinkedHashSet<>();
2716 
2717         if ( includeDependencySources )
2718         {
2719             try
2720             {
2721                 resolveDependencyBundles();
2722             }
2723             catch ( IOException e )
2724             {
2725                 throw new MavenReportException(
2726                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
2727             }
2728 
2729             if ( isNotEmpty( dependencyJavadocBundles ) )
2730             {
2731                 for ( JavadocBundle bundle : dependencyJavadocBundles )
2732                 {
2733                     JavadocOptions options = bundle.getOptions();
2734                     if ( options != null && isNotEmpty( options.getExcludePackageNames() ) )
2735                     {
2736                         excluded.addAll( options.getExcludePackageNames() );
2737                     }
2738                 }
2739             }
2740         }
2741 
2742         // for the specified excludePackageNames
2743         if ( StringUtils.isNotEmpty( excludePackageNames ) )
2744         {
2745             List<String> packageNames = Arrays.asList( excludePackageNames.split( "[,:;]" ) );
2746             excluded.addAll( trimValues( packageNames ) );
2747         }
2748 
2749         return excluded;
2750     }
2751 
2752     private static List<String> trimValues( List<String> items )
2753     {
2754         List<String> result = new ArrayList<>( items.size() );
2755         for ( String item : items )
2756         {
2757             String trimmed = item.trim();
2758             if ( StringUtils.isEmpty( trimmed ) )
2759             {
2760                 continue;
2761             }
2762             result.add( trimmed );
2763         }
2764         return result;
2765     }
2766 
2767     /**
2768      * Method that gets the classpath and modulepath elements that will be specified in the javadoc
2769      * <code>-classpath</code> and <code>--module-path</code> parameter.
2770      * Since we have all the sources of the current reactor, it is sufficient to consider the
2771      * dependencies of the reactor modules, excluding the module artifacts which may not yet be available
2772      * when the reactor project is built for the first time.
2773      *
2774      * @return all classpath elements
2775      * @throws MavenReportException if any.
2776      */
2777     private Collection<File> getPathElements()
2778         throws MavenReportException
2779     {
2780         Set<File> classpathElements = new LinkedHashSet<>();
2781         Map<String, Artifact> compileArtifactMap = new LinkedHashMap<>();
2782 
2783         if ( isTest() )
2784         {
2785             classpathElements.addAll( getProjectBuildOutputDirs( project ) );
2786         }
2787 
2788         populateCompileArtifactMap( compileArtifactMap, project.getArtifacts() );
2789 
2790         if ( isAggregator() )
2791         {
2792             Collection<MavenProject> aggregatorProjects = getAggregatedProjects();
2793 
2794             List<String> reactorArtifacts = new ArrayList<>();
2795             for ( MavenProject p : aggregatorProjects )
2796             {
2797                 reactorArtifacts.add( p.getGroupId() + ':' + p.getArtifactId() );
2798             }
2799 
2800             TransformableFilter dependencyFilter = new AndFilter( Arrays.asList(
2801                                                                      new PatternExclusionsFilter( reactorArtifacts ),
2802                                                                      getDependencyScopeFilter() ) );
2803 
2804             for ( MavenProject subProject : aggregatorProjects )
2805             {
2806                 if ( subProject != project )
2807                 {
2808                     File projectArtifactFile = getArtifactFile( subProject );
2809                     if ( projectArtifactFile != null )
2810                     {
2811                         classpathElements.add( projectArtifactFile );
2812                     }
2813                     else
2814                     {
2815                         classpathElements.addAll( getProjectBuildOutputDirs( subProject ) );
2816                     }
2817 
2818                     try
2819                     {
2820                         StringBuilder sb = new StringBuilder();
2821 
2822                         sb.append( "Compiled artifacts for " );
2823                         sb.append( subProject.getGroupId() ).append( ":" );
2824                         sb.append( subProject.getArtifactId() ).append( ":" );
2825                         sb.append( subProject.getVersion() ).append( '\n' );
2826 
2827                         ProjectBuildingRequest buildingRequest = getProjectBuildingRequest( subProject );
2828 
2829                         List<Dependency> managedDependencies = null;
2830                         if ( subProject.getDependencyManagement() != null )
2831                         {
2832                             managedDependencies = subProject.getDependencyManagement().getDependencies();
2833                         }
2834 
2835                         for ( ArtifactResult artifactResult
2836                                     : dependencyResolver.resolveDependencies( buildingRequest,
2837                                                                               subProject.getDependencies(),
2838                                                                               managedDependencies,
2839                                                                               dependencyFilter ) )
2840                         {
2841                             populateCompileArtifactMap( compileArtifactMap,
2842                                                         Collections.singletonList( artifactResult.getArtifact() ) );
2843 
2844                             sb.append( artifactResult.getArtifact().getFile() ).append( '\n' );
2845                         }
2846 
2847                         if ( getLog().isDebugEnabled() )
2848                         {
2849                             getLog().debug( sb.toString() );
2850                         }
2851 
2852                     }
2853                     catch ( DependencyResolverException e )
2854                     {
2855                         throw new MavenReportException( e.getMessage(), e );
2856                     }
2857                 }
2858             }
2859         }
2860 
2861         for ( Artifact a : compileArtifactMap.values() )
2862         {
2863             classpathElements.add( a.getFile() );
2864         }
2865 
2866         if ( additionalDependencies != null )
2867         {
2868             for ( Dependency dependency : additionalDependencies )
2869             {
2870                 Artifact artifact = resolveDependency( dependency );
2871                 getLog().debug( "add additional artifact with path " + artifact.getFile() );
2872                 classpathElements.add( artifact.getFile() );
2873             }
2874         }
2875 
2876         return classpathElements;
2877     }
2878 
2879     protected ScopeFilter getDependencyScopeFilter()
2880     {
2881         return ScopeFilter.including( Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_SYSTEM );
2882     }
2883 
2884     /**
2885      * @param dependency {@link Dependency}
2886      * @return {@link Artifact}
2887      * @throws MavenReportException when artifact could not be resolved
2888      */
2889     public Artifact resolveDependency( Dependency dependency )
2890         throws MavenReportException
2891     {
2892         DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
2893         coordinate.setGroupId( dependency.getGroupId() );
2894         coordinate.setArtifactId( dependency.getArtifactId() );
2895         coordinate.setVersion( dependency.getVersion() );
2896         coordinate.setClassifier( dependency.getClassifier() );
2897         coordinate.setExtension( artifactHandlerManager.getArtifactHandler( dependency.getType() ).getExtension() );
2898 
2899         try
2900         {
2901             return artifactResolver.resolveArtifact( getProjectBuildingRequest( project ), coordinate ).getArtifact();
2902         }
2903         catch ( ArtifactResolverException e )
2904         {
2905             throw new MavenReportException( "artifact resolver problem - " + e.getMessage(), e );
2906         }
2907     }
2908 
2909 
2910     //TODO remove the part with ToolchainManager lookup once we depend on
2911     //3.0.9 (have it as prerequisite). Define as regular component field then.
2912     protected final Toolchain getToolchain()
2913     {
2914         Toolchain tc = null;
2915 
2916         if ( jdkToolchain != null )
2917         {
2918             // Maven 3.3.1 has plugin execution scoped Toolchain Support
2919             try
2920             {
2921                 Method getToolchainsMethod =
2922                     toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class, String.class,
2923                                                            Map.class );
2924 
2925                 @SuppressWarnings( "unchecked" )
2926                 List<Toolchain> tcs =
2927                     (List<Toolchain>) getToolchainsMethod.invoke( toolchainManager, session, "jdk",
2928                                                                   jdkToolchain );
2929 
2930                 if ( tcs != null && tcs.size() > 0 )
2931                 {
2932                     tc = tcs.get( 0 );
2933                 }
2934             }
2935             catch ( SecurityException | ReflectiveOperationException e )
2936             {
2937                 // ignore
2938             }
2939         }
2940 
2941         if ( tc == null )
2942         {
2943             tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
2944         }
2945 
2946         return tc;
2947     }
2948 
2949     /**
2950      * Method to put the artifacts in the hashmap.
2951      *
2952      * @param compileArtifactMap the hashmap that will contain the artifacts
2953      * @param artifactList       the list of artifacts that will be put in the map
2954      * @throws MavenReportException if any
2955      */
2956     private void populateCompileArtifactMap( Map<String, Artifact> compileArtifactMap,
2957                                              Collection<Artifact> artifactList )
2958         throws MavenReportException
2959     {
2960         if ( artifactList == null )
2961         {
2962             return;
2963         }
2964 
2965         for ( Artifact newArtifact : artifactList )
2966         {
2967             File file = newArtifact.getFile();
2968 
2969             if ( file == null )
2970             {
2971                 throw new MavenReportException(
2972                     "Error in plugin descriptor - " + "dependency was not resolved for artifact: "
2973                         + newArtifact.getGroupId() + ":" + newArtifact.getArtifactId() + ":"
2974                         + newArtifact.getVersion() );
2975             }
2976 
2977             if ( compileArtifactMap.get( newArtifact.getDependencyConflictId() ) != null )
2978             {
2979                 Artifact oldArtifact = compileArtifactMap.get( newArtifact.getDependencyConflictId() );
2980 
2981                 ArtifactVersion oldVersion = new DefaultArtifactVersion( oldArtifact.getVersion() );
2982                 ArtifactVersion newVersion = new DefaultArtifactVersion( newArtifact.getVersion() );
2983                 if ( newVersion.compareTo( oldVersion ) > 0 )
2984                 {
2985                     compileArtifactMap.put( newArtifact.getDependencyConflictId(), newArtifact );
2986                 }
2987             }
2988             else
2989             {
2990                 compileArtifactMap.put( newArtifact.getDependencyConflictId(), newArtifact );
2991             }
2992         }
2993     }
2994 
2995     /**
2996      * Method that sets the bottom text that will be displayed on the bottom of the
2997      * javadocs.
2998      *
2999      * @return a String that contains the text that will be displayed at the bottom of the javadoc
3000      */
3001     private String getBottomText()
3002     {
3003         final String year;
3004         String buildTime = project.getProperties().getProperty( PROJECT_BUILD_OUTPUTTIMESTAMP );
3005         if ( buildTime != null )
3006         {
3007             year = String.valueOf( DateTimeFormatter.ISO_DATE_TIME.parse( buildTime ).get( ChronoField.YEAR ) );
3008         }
3009         else
3010         {
3011             getLog().debug( "Using current year due to unavailable property '" + PROJECT_BUILD_OUTPUTTIMESTAMP + "'" );
3012             int currentYear = Calendar.getInstance().get( Calendar.YEAR );
3013             year = String.valueOf( currentYear );
3014         }
3015 
3016         String inceptionYear = project.getInceptionYear();
3017 
3018         String theBottom = StringUtils.replace( this.bottom, "{currentYear}", year );
3019 
3020         if ( inceptionYear != null )
3021         {
3022             if ( inceptionYear.equals( year ) )
3023             {
3024                 theBottom = StringUtils.replace( theBottom, "{inceptionYear}&#x2013;", "" );
3025             }
3026             else
3027             {
3028                 theBottom = StringUtils.replace( theBottom, "{inceptionYear}", inceptionYear );
3029             }
3030         }
3031         else
3032         {
3033             theBottom = StringUtils.replace( theBottom, "{inceptionYear}&#x2013;", "" );
3034         }
3035 
3036         if ( project.getOrganization() == null )
3037         {
3038             theBottom = StringUtils.replace( theBottom, " {organizationName}", "" );
3039         }
3040         else
3041         {
3042             if ( StringUtils.isNotEmpty( project.getOrganization().getName() ) )
3043             {
3044                 if ( StringUtils.isNotEmpty( project.getOrganization().getUrl() ) )
3045                 {
3046                     theBottom = StringUtils.replace( theBottom, "{organizationName}",
3047                                                      "<a href=\"" + project.getOrganization().getUrl() + "\">"
3048                                                          + project.getOrganization().getName() + "</a>" );
3049                 }
3050                 else
3051                 {
3052                     theBottom =
3053                         StringUtils.replace( theBottom, "{organizationName}", project.getOrganization().getName() );
3054                 }
3055             }
3056             else
3057             {
3058                 theBottom = StringUtils.replace( theBottom, " {organizationName}", "" );
3059             }
3060         }
3061 
3062         return theBottom;
3063     }
3064 
3065     /**
3066      * Method to get the stylesheet path file to be used by the Javadoc Tool.
3067      * <br/>
3068      * If the {@link #stylesheetfile} is empty, return the file as String definded by {@link #stylesheet} value.
3069      * <br/>
3070      * If the {@link #stylesheetfile} is defined, return the file as String.
3071      * <br/>
3072      * Note: since 2.6, the {@link #stylesheetfile} could be a path from a resource in the project source
3073      * directories (i.e. <code>src/main/java</code>, <code>src/main/resources</code> or <code>src/main/javadoc</code>)
3074      * or from a resource in the Javadoc plugin dependencies.
3075      *
3076      * @param javadocOutputDirectory the output directory
3077      * @return the stylesheet file absolute path as String.
3078      * @see #getResource(List, String)
3079      */
3080     private Optional<File> getStylesheetFile( final File javadocOutputDirectory )
3081     {
3082         if ( StringUtils.isEmpty( stylesheetfile ) )
3083         {
3084             if ( "java".equalsIgnoreCase( stylesheet ) )
3085             {
3086                 // use the default Javadoc tool stylesheet
3087                 return Optional.empty();
3088             }
3089 
3090             // maven, see #copyDefaultStylesheet(File)
3091             return Optional.of( new File( javadocOutputDirectory, DEFAULT_CSS_NAME ) );
3092         }
3093 
3094         if ( new File( stylesheetfile ).exists() )
3095         {
3096             return Optional.of( new File( stylesheetfile ) );
3097         }
3098 
3099         return getResource( new File( javadocOutputDirectory, DEFAULT_CSS_NAME ), stylesheetfile );
3100     }
3101     
3102     private void addAddStyleSheets( List<String> arguments ) throws MavenReportException
3103     {
3104         if ( addStylesheets == null )
3105         {
3106             return;
3107         }
3108         
3109         for ( String addStylesheet : addStylesheets )
3110         {
3111             Optional<File> styleSheet = getAddStylesheet( getJavadocDirectory(), addStylesheet );
3112             
3113             if ( styleSheet.isPresent() )
3114             {
3115                 addArgIfNotEmpty( arguments, "--add-stylesheet",
3116                                   JavadocUtil.quotedPathArgument( styleSheet.get().getAbsolutePath() ),
3117                                   JavaVersion.parse( "10" ) );
3118             }
3119         }
3120     }
3121     
3122     
3123     private Optional<File> getAddStylesheet( final File javadocOutputDirectory, final String stylesheet )
3124             throws MavenReportException
3125     {
3126         if ( StringUtils.isEmpty( stylesheet ) )
3127         {
3128             return Optional.empty();
3129         }
3130         
3131         File addstylesheetfile = new File( getJavadocDirectory(), stylesheet );
3132         if ( addstylesheetfile.exists() )
3133         {
3134             Optional<File> stylesheetfile = getStylesheetFile( javadocOutputDirectory );
3135             if ( stylesheetfile.isPresent() )
3136             {
3137                 if ( stylesheetfile.get().getName().equals( addstylesheetfile.getName() ) )
3138                 {
3139                     throw new MavenReportException( "additional stylesheet must have a different name "
3140                                                         + "than stylesheetfile: " + stylesheetfile.get().getName() );
3141                 }
3142             }
3143             
3144             return Optional.of( addstylesheetfile );
3145         }
3146 
3147         throw new MavenReportException( "additional stylesheet file does not exist: " 
3148                                             + addstylesheetfile.getAbsolutePath() );
3149     }
3150 
3151     /**
3152      * Method to get the help file to be used by the Javadoc Tool.
3153      * <br/>
3154      * Since 2.6, the {@link #helpfile} could be a path from a resource in the project source
3155      * directories (i.e. <code>src/main/java</code>, <code>src/main/resources</code> or <code>src/main/javadoc</code>)
3156      * or from a resource in the Javadoc plugin dependencies.
3157      *
3158      * @param javadocOutputDirectory the output directory.
3159      * @return the help file absolute path as String.
3160      * @see #getResource(File, String)
3161      * @since 2.6
3162      */
3163     private Optional<File> getHelpFile( final File javadocOutputDirectory )
3164     {
3165         if ( StringUtils.isEmpty( helpfile ) )
3166         {
3167             return Optional.empty();
3168         }
3169 
3170         if ( new File( helpfile ).exists() )
3171         {
3172             return Optional.of( new File( helpfile ) );
3173         }
3174 
3175         return getResource( new File( javadocOutputDirectory, "help-doc.html" ), helpfile );
3176     }
3177 
3178     /**
3179      * Method to get the access level for the classes and members to be shown in the generated javadoc.
3180      * If the specified access level is not public, protected, package or private, the access level
3181      * is set to protected.
3182      *
3183      * @return the access level
3184      */
3185     private String getAccessLevel()
3186     {
3187         String accessLevel;
3188         if ( "public".equalsIgnoreCase( show ) || "protected".equalsIgnoreCase( show ) || "package".equalsIgnoreCase(
3189             show ) || "private".equalsIgnoreCase( show ) )
3190         {
3191             accessLevel = "-" + show;
3192         }
3193         else
3194         {
3195             if ( getLog().isErrorEnabled() )
3196             {
3197                 getLog().error( "Unrecognized access level to show '" + show + "'. Defaulting to protected." );
3198             }
3199             accessLevel = "-protected";
3200         }
3201 
3202         return accessLevel;
3203     }
3204 
3205     /**
3206      * Method to get the path of the bootclass artifacts used in the <code>-bootclasspath</code> option.
3207      *
3208      * @return a string that contains bootclass path, separated by the System pathSeparator string
3209      *         (colon (<code>:</code>) on Solaris or semi-colon (<code>;</code>) on Windows).
3210      * @throws MavenReportException if any
3211      * @see File#pathSeparator
3212      */
3213     private String getBootclassPath()
3214         throws MavenReportException
3215     {
3216         Set<BootclasspathArtifact> bootclasspathArtifacts = collectBootClasspathArtifacts();
3217 
3218         List<String> bootclassPath = new ArrayList<>();
3219         for ( BootclasspathArtifact aBootclasspathArtifact : bootclasspathArtifacts )
3220         {
3221             if ( ( StringUtils.isNotEmpty( aBootclasspathArtifact.getGroupId() ) ) && ( StringUtils.isNotEmpty(
3222                 aBootclasspathArtifact.getArtifactId() ) ) && ( StringUtils.isNotEmpty(
3223                 aBootclasspathArtifact.getVersion() ) ) )
3224             {
3225                 bootclassPath.addAll( getArtifactsAbsolutePath( aBootclasspathArtifact ) );
3226             }
3227         }
3228 
3229         bootclassPath = JavadocUtil.pruneFiles( bootclassPath );
3230 
3231         StringBuilder path = new StringBuilder();
3232         path.append( StringUtils.join( bootclassPath.iterator(), File.pathSeparator ) );
3233 
3234         if ( StringUtils.isNotEmpty( bootclasspath ) )
3235         {
3236             path.append( JavadocUtil.unifyPathSeparator( bootclasspath ) );
3237         }
3238 
3239         return path.toString();
3240     }
3241 
3242     /**
3243      * Method to get the path of the doclet artifacts used in the <code>-docletpath</code> option.
3244      * <p/>
3245      * Either docletArtifact or doclectArtifacts can be defined and used, not both, docletArtifact
3246      * takes precedence over doclectArtifacts. docletPath is always appended to any result path
3247      * definition.
3248      *
3249      * @return a string that contains doclet path, separated by the System pathSeparator string
3250      *         (colon (<code>:</code>) on Solaris or semi-colon (<code>;</code>) on Windows).
3251      * @throws MavenReportException if any
3252      * @see File#pathSeparator
3253      */
3254     private String getDocletPath()
3255         throws MavenReportException
3256     {
3257         Set<DocletArtifact> docletArtifacts = collectDocletArtifacts();
3258         List<String> pathParts = new ArrayList<>();
3259 
3260         for ( DocletArtifact docletArtifact : docletArtifacts )
3261         {
3262             if ( !isDocletArtifactEmpty( docletArtifact ) )
3263             {
3264                 pathParts.addAll( getArtifactsAbsolutePath( docletArtifact ) );
3265             }
3266         }
3267 
3268         if ( !StringUtils.isEmpty( docletPath ) )
3269         {
3270             pathParts.add( JavadocUtil.unifyPathSeparator( docletPath ) );
3271         }
3272 
3273         String path = StringUtils.join( pathParts.iterator(), File.pathSeparator );
3274 
3275         if ( StringUtils.isEmpty( path ) && getLog().isWarnEnabled() )
3276         {
3277             getLog().warn( "No docletpath option was found. Please review <docletpath/> or <docletArtifact/>"
3278                                + " or <doclets/>." );
3279         }
3280 
3281         return path;
3282     }
3283 
3284     /**
3285      * Verify if a doclet artifact is empty or not
3286      *
3287      * @param aDocletArtifact could be null
3288      * @return <code>true</code> if aDocletArtifact or the groupId/artifactId/version of the doclet artifact is null,
3289      *         <code>false</code> otherwise.
3290      */
3291     private boolean isDocletArtifactEmpty( DocletArtifact aDocletArtifact )
3292     {
3293         if ( aDocletArtifact == null )
3294         {
3295             return true;
3296         }
3297 
3298         return StringUtils.isEmpty( aDocletArtifact.getGroupId() ) && StringUtils.isEmpty(
3299             aDocletArtifact.getArtifactId() ) && StringUtils.isEmpty( aDocletArtifact.getVersion() );
3300     }
3301 
3302     /**
3303      * Method to get the path of the taglet artifacts used in the <code>-tagletpath</code> option.
3304      *
3305      * @return a string that contains taglet path, separated by the System pathSeparator string
3306      *         (colon (<code>:</code>) on Solaris or semi-colon (<code>;</code>) on Windows).
3307      * @throws MavenReportException if any
3308      * @see File#pathSeparator
3309      */
3310     private String getTagletPath()
3311         throws MavenReportException
3312     {
3313         Set<TagletArtifact> tArtifacts = collectTagletArtifacts();
3314         Collection<String> pathParts = new ArrayList<>();
3315 
3316         for ( TagletArtifact tagletArtifact : tArtifacts )
3317         {
3318             if ( ( tagletArtifact != null ) && ( StringUtils.isNotEmpty( tagletArtifact.getGroupId() ) )
3319                 && ( StringUtils.isNotEmpty( tagletArtifact.getArtifactId() ) ) && ( StringUtils.isNotEmpty(
3320                 tagletArtifact.getVersion() ) ) )
3321             {
3322                 pathParts.addAll( getArtifactsAbsolutePath( tagletArtifact ) );
3323             }
3324         }
3325 
3326         Set<Taglet> taglets = collectTaglets();
3327         for ( Taglet taglet : taglets )
3328         {
3329             if ( taglet == null )
3330             {
3331                 continue;
3332             }
3333 
3334             if ( ( taglet.getTagletArtifact() != null ) && ( StringUtils.isNotEmpty(
3335                 taglet.getTagletArtifact().getGroupId() ) ) && ( StringUtils.isNotEmpty(
3336                 taglet.getTagletArtifact().getArtifactId() ) ) && ( StringUtils.isNotEmpty(
3337                 taglet.getTagletArtifact().getVersion() ) ) )
3338             {
3339                 pathParts.addAll( JavadocUtil.pruneFiles( getArtifactsAbsolutePath( taglet.getTagletArtifact() ) ) );
3340             }
3341             else if ( StringUtils.isNotEmpty( taglet.getTagletpath() ) )
3342             {
3343                 for ( Path dir : JavadocUtil.pruneDirs( project, Collections.singletonList( taglet.getTagletpath() ) ) )
3344                 {
3345                     pathParts.add( dir.toString()  );
3346                 }
3347             }
3348         }
3349 
3350         StringBuilder path = new StringBuilder();
3351         path.append( StringUtils.join( pathParts.iterator(), File.pathSeparator ) );
3352 
3353         if ( StringUtils.isNotEmpty( tagletpath ) )
3354         {
3355             path.append( JavadocUtil.unifyPathSeparator( tagletpath ) );
3356         }
3357 
3358         return path.toString();
3359     }
3360 
3361     private Set<String> collectLinks()
3362         throws MavenReportException
3363     {
3364         Set<String> links = new LinkedHashSet<>();
3365 
3366         if ( includeDependencySources )
3367         {
3368             try
3369             {
3370                 resolveDependencyBundles();
3371             }
3372             catch ( IOException e )
3373             {
3374                 throw new MavenReportException(
3375                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
3376             }
3377 
3378             if ( isNotEmpty( dependencyJavadocBundles ) )
3379             {
3380                 for ( JavadocBundle bundle : dependencyJavadocBundles )
3381                 {
3382                     JavadocOptions options = bundle.getOptions();
3383                     if ( options != null && isNotEmpty( options.getLinks() ) )
3384                     {
3385                         links.addAll( options.getLinks() );
3386                     }
3387                 }
3388             }
3389         }
3390 
3391         if ( isNotEmpty( this.links ) )
3392         {
3393             links.addAll( this.links );
3394         }
3395 
3396         links.addAll( getDependenciesLinks() );
3397 
3398         return followLinks( links );
3399     }
3400 
3401     private Set<Group> collectGroups()
3402         throws MavenReportException
3403     {
3404         Set<Group> groups = new LinkedHashSet<>();
3405 
3406         if ( includeDependencySources )
3407         {
3408             try
3409             {
3410                 resolveDependencyBundles();
3411             }
3412             catch ( IOException e )
3413             {
3414                 throw new MavenReportException(
3415                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
3416             }
3417 
3418             if ( isNotEmpty( dependencyJavadocBundles ) )
3419             {
3420                 for ( JavadocBundle bundle : dependencyJavadocBundles )
3421                 {
3422                     JavadocOptions options = bundle.getOptions();
3423                     if ( options != null && isNotEmpty( options.getGroups() ) )
3424                     {
3425                         groups.addAll( options.getGroups() );
3426                     }
3427                 }
3428             }
3429         }
3430 
3431         if ( this.groups != null && this.groups.length > 0 )
3432         {
3433             groups.addAll( Arrays.asList( this.groups ) );
3434         }
3435 
3436         return groups;
3437     }
3438 
3439     private Set<ResourcesArtifact> collectResourcesArtifacts()
3440         throws MavenReportException
3441     {
3442         Set<ResourcesArtifact> result = new LinkedHashSet<>();
3443 
3444         if ( includeDependencySources )
3445         {
3446             try
3447             {
3448                 resolveDependencyBundles();
3449             }
3450             catch ( IOException e )
3451             {
3452                 throw new MavenReportException(
3453                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
3454             }
3455 
3456             if ( isNotEmpty( dependencyJavadocBundles ) )
3457             {
3458                 for ( JavadocBundle bundle : dependencyJavadocBundles )
3459                 {
3460                     JavadocOptions options = bundle.getOptions();
3461                     if ( options != null && isNotEmpty( options.getResourcesArtifacts() ) )
3462                     {
3463                         result.addAll( options.getResourcesArtifacts() );
3464                     }
3465                 }
3466             }
3467         }
3468 
3469         if ( this.resourcesArtifacts != null && this.resourcesArtifacts.length > 0 )
3470         {
3471             result.addAll( Arrays.asList( this.resourcesArtifacts ) );
3472         }
3473 
3474         return result;
3475     }
3476 
3477     private Set<BootclasspathArtifact> collectBootClasspathArtifacts()
3478         throws MavenReportException
3479     {
3480         Set<BootclasspathArtifact> result = new LinkedHashSet<>();
3481 
3482         if ( includeDependencySources )
3483         {
3484             try
3485             {
3486                 resolveDependencyBundles();
3487             }
3488             catch ( IOException e )
3489             {
3490                 throw new MavenReportException(
3491                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
3492             }
3493 
3494             if ( isNotEmpty( dependencyJavadocBundles ) )
3495             {
3496                 for ( JavadocBundle bundle : dependencyJavadocBundles )
3497                 {
3498                     JavadocOptions options = bundle.getOptions();
3499                     if ( options != null && isNotEmpty( options.getBootclasspathArtifacts() ) )
3500                     {
3501                         result.addAll( options.getBootclasspathArtifacts() );
3502                     }
3503                 }
3504             }
3505         }
3506 
3507         if ( this.bootclasspathArtifacts != null && this.bootclasspathArtifacts.length > 0 )
3508         {
3509             result.addAll( Arrays.asList( this.bootclasspathArtifacts ) );
3510         }
3511 
3512         return result;
3513     }
3514 
3515     private Set<OfflineLink> collectOfflineLinks()
3516         throws MavenReportException
3517     {
3518         Set<OfflineLink> result = new LinkedHashSet<>();
3519 
3520         OfflineLink javaApiLink = getDefaultJavadocApiLink();
3521         if ( javaApiLink != null )
3522         {
3523             result.add( javaApiLink );
3524         }
3525 
3526         if ( includeDependencySources )
3527         {
3528             try
3529             {
3530                 resolveDependencyBundles();
3531             }
3532             catch ( IOException e )
3533             {
3534                 throw new MavenReportException(
3535                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
3536             }
3537 
3538             if ( isNotEmpty( dependencyJavadocBundles ) )
3539             {
3540                 for ( JavadocBundle bundle : dependencyJavadocBundles )
3541                 {
3542                     JavadocOptions options = bundle.getOptions();
3543                     if ( options != null && isNotEmpty( options.getOfflineLinks() ) )
3544                     {
3545                         result.addAll( options.getOfflineLinks() );
3546                     }
3547                 }
3548             }
3549         }
3550 
3551         if ( this.offlineLinks != null && this.offlineLinks.length > 0 )
3552         {
3553             result.addAll( Arrays.asList( this.offlineLinks ) );
3554         }
3555 
3556         return result;
3557     }
3558 
3559     private Set<Tag> collectTags()
3560         throws MavenReportException
3561     {
3562         Set<Tag> tags = new LinkedHashSet<>();
3563 
3564         if ( includeDependencySources )
3565         {
3566             try
3567             {
3568                 resolveDependencyBundles();
3569             }
3570             catch ( IOException e )
3571             {
3572                 throw new MavenReportException(
3573                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
3574             }
3575 
3576             if ( isNotEmpty( dependencyJavadocBundles ) )
3577             {
3578                 for ( JavadocBundle bundle : dependencyJavadocBundles )
3579                 {
3580                     JavadocOptions options = bundle.getOptions();
3581                     if ( options != null && isNotEmpty( options.getTags() ) )
3582                     {
3583                         tags.addAll( options.getTags() );
3584                     }
3585                 }
3586             }
3587         }
3588 
3589         if ( this.tags != null && this.tags.length > 0 )
3590         {
3591             tags.addAll( Arrays.asList( this.tags ) );
3592         }
3593 
3594         return tags;
3595     }
3596 
3597     private Set<TagletArtifact> collectTagletArtifacts()
3598         throws MavenReportException
3599     {
3600         Set<TagletArtifact> tArtifacts = new LinkedHashSet<>();
3601 
3602         if ( includeDependencySources )
3603         {
3604             try
3605             {
3606                 resolveDependencyBundles();
3607             }
3608             catch ( IOException e )
3609             {
3610                 throw new MavenReportException(
3611                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
3612             }
3613 
3614             if ( isNotEmpty( dependencyJavadocBundles ) )
3615             {
3616                 for ( JavadocBundle bundle : dependencyJavadocBundles )
3617                 {
3618                     JavadocOptions options = bundle.getOptions();
3619                     if ( options != null && isNotEmpty( options.getTagletArtifacts() ) )
3620                     {
3621                         tArtifacts.addAll( options.getTagletArtifacts() );
3622                     }
3623                 }
3624             }
3625         }
3626 
3627         if ( tagletArtifact != null )
3628         {
3629             tArtifacts.add( tagletArtifact );
3630         }
3631 
3632         if ( tagletArtifacts != null && tagletArtifacts.length > 0 )
3633         {
3634             tArtifacts.addAll( Arrays.asList( tagletArtifacts ) );
3635         }
3636 
3637         return tArtifacts;
3638     }
3639 
3640     private Set<DocletArtifact> collectDocletArtifacts()
3641         throws MavenReportException
3642     {
3643         Set<DocletArtifact> dArtifacts = new LinkedHashSet<>();
3644 
3645         if ( includeDependencySources )
3646         {
3647             try
3648             {
3649                 resolveDependencyBundles();
3650             }
3651             catch ( IOException e )
3652             {
3653                 throw new MavenReportException(
3654                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
3655             }
3656 
3657             if ( isNotEmpty( dependencyJavadocBundles ) )
3658             {
3659                 for ( JavadocBundle bundle : dependencyJavadocBundles )
3660                 {
3661                     JavadocOptions options = bundle.getOptions();
3662                     if ( options != null && isNotEmpty( options.getDocletArtifacts() ) )
3663                     {
3664                         dArtifacts.addAll( options.getDocletArtifacts() );
3665                     }
3666                 }
3667             }
3668         }
3669 
3670         if ( docletArtifact != null )
3671         {
3672             dArtifacts.add( docletArtifact );
3673         }
3674 
3675         if ( docletArtifacts != null && docletArtifacts.length > 0 )
3676         {
3677             dArtifacts.addAll( Arrays.asList( docletArtifacts ) );
3678         }
3679 
3680         return dArtifacts;
3681     }
3682 
3683     private Set<Taglet> collectTaglets()
3684         throws MavenReportException
3685     {
3686         Set<Taglet> result = new LinkedHashSet<>();
3687 
3688         if ( includeDependencySources )
3689         {
3690             try
3691             {
3692                 resolveDependencyBundles();
3693             }
3694             catch ( IOException e )
3695             {
3696                 throw new MavenReportException(
3697                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
3698             }
3699 
3700             if ( isNotEmpty( dependencyJavadocBundles ) )
3701             {
3702                 for ( JavadocBundle bundle : dependencyJavadocBundles )
3703                 {
3704                     JavadocOptions options = bundle.getOptions();
3705                     if ( options != null && isNotEmpty( options.getTaglets() ) )
3706                     {
3707                         result.addAll( options.getTaglets() );
3708                     }
3709                 }
3710             }
3711         }
3712 
3713         if ( taglets != null && taglets.length > 0 )
3714         {
3715             result.addAll( Arrays.asList( taglets ) );
3716         }
3717 
3718         return result;
3719     }
3720 
3721     /**
3722      * Return the Javadoc artifact path and its transitive dependencies path from the local repository
3723      *
3724      * @param javadocArtifact not null
3725      * @return a list of locale artifacts absolute path
3726      * @throws MavenReportException if any
3727      */
3728     private List<String> getArtifactsAbsolutePath( JavadocPathArtifact javadocArtifact )
3729         throws MavenReportException
3730     {
3731         if ( ( StringUtils.isEmpty( javadocArtifact.getGroupId() ) ) && ( StringUtils.isEmpty(
3732             javadocArtifact.getArtifactId() ) ) && ( StringUtils.isEmpty( javadocArtifact.getVersion() ) ) )
3733         {
3734             return Collections.emptyList();
3735         }
3736 
3737         List<String> path = new ArrayList<>();
3738 
3739         try
3740         {
3741             Artifact artifact = createAndResolveArtifact( javadocArtifact );
3742             path.add( artifact.getFile().getAbsolutePath() );
3743 
3744             DefaultDependableCoordinate coordinate = new DefaultDependableCoordinate();
3745             coordinate.setGroupId( javadocArtifact.getGroupId() );
3746             coordinate.setArtifactId( javadocArtifact.getArtifactId() );
3747             coordinate.setVersion( javadocArtifact.getVersion() );
3748 
3749             Iterable<ArtifactResult> deps =
3750                 dependencyResolver.resolveDependencies( getProjectBuildingRequest( project ), coordinate,
3751                                                         ScopeFilter.including( "compile", "provided" ) );
3752             for ( ArtifactResult a : deps )
3753             {
3754                 path.add( a.getArtifact().getFile().getAbsolutePath() );
3755             }
3756 
3757             return path;
3758         }
3759         catch ( ArtifactResolverException e )
3760         {
3761             throw new MavenReportException( "Unable to resolve artifact:" + javadocArtifact, e );
3762         }
3763         catch ( DependencyResolverException e )
3764         {
3765             throw new MavenReportException( "Unable to resolve dependencies for:" + javadocArtifact, e );
3766         }
3767     }
3768 
3769     /**
3770      * creates an {@link Artifact} representing the configured {@link JavadocPathArtifact} and resolves it.
3771      *
3772      * @param javadocArtifact the {@link JavadocPathArtifact} to resolve
3773      * @return a resolved {@link Artifact}
3774      * @throws ArtifactResolverException issue while resolving artifact
3775      */
3776     private Artifact createAndResolveArtifact( JavadocPathArtifact javadocArtifact )
3777         throws ArtifactResolverException
3778     {
3779         DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
3780         coordinate.setGroupId( javadocArtifact.getGroupId() );
3781         coordinate.setArtifactId( javadocArtifact.getArtifactId() );
3782         coordinate.setVersion( javadocArtifact.getVersion() );
3783 
3784         return artifactResolver.resolveArtifact( getProjectBuildingRequest( project ), coordinate ).getArtifact();
3785     }
3786 
3787     /**
3788      * Method that adds/sets the java memory parameters in the command line execution.
3789      *
3790      * @param cmd    the command line execution object where the argument will be added
3791      * @param arg    the argument parameter name
3792      * @param memory the JVM memory value to be set
3793      * @see JavadocUtil#parseJavadocMemory(String)
3794      */
3795     private void addMemoryArg( Commandline cmd, String arg, String memory )
3796     {
3797         if ( StringUtils.isNotEmpty( memory ) )
3798         {
3799             try
3800             {
3801                 cmd.createArg().setValue( "-J" + arg + JavadocUtil.parseJavadocMemory( memory ) );
3802             }
3803             catch ( IllegalArgumentException e )
3804             {
3805                 if ( getLog().isErrorEnabled() )
3806                 {
3807                     getLog().error( "Malformed memory pattern for '" + arg + memory + "'. Ignore this option." );
3808                 }
3809             }
3810         }
3811     }
3812 
3813     /**
3814      * Method that adds/sets the javadoc proxy parameters in the command line execution.
3815      *
3816      * @param cmd the command line execution object where the argument will be added
3817      */
3818     private void addProxyArg( Commandline cmd )
3819     {
3820         if ( settings == null || settings.getProxies().isEmpty() )
3821         {
3822             return;
3823         }
3824 
3825         Map<String, Proxy> activeProxies = new HashMap<>();
3826 
3827         for ( Proxy proxy : settings.getProxies() )
3828         {
3829             if ( proxy.isActive() )
3830             {
3831                 String protocol = proxy.getProtocol();
3832 
3833                 if ( !activeProxies.containsKey( protocol ) )
3834             {
3835                     activeProxies.put( protocol, proxy );
3836                 }
3837             }
3838             }
3839 
3840         if ( activeProxies.containsKey( "https" ) )
3841         {
3842             Proxy httpsProxy = activeProxies.get( "https" );
3843             if ( StringUtils.isNotEmpty( httpsProxy.getHost() ) )
3844             {
3845                 cmd.createArg().setValue( "-J-Dhttps.proxyHost=" + httpsProxy.getHost() );
3846                 cmd.createArg().setValue( "-J-Dhttps.proxyPort=" + httpsProxy.getPort() );
3847 
3848                 if ( StringUtils.isNotEmpty( httpsProxy.getNonProxyHosts() )
3849                      && ( !activeProxies.containsKey( "http" )
3850                           || StringUtils.isEmpty( activeProxies.get( "http" ).getNonProxyHosts() ) ) )
3851                 {
3852                     cmd.createArg().setValue( "-J-Dhttp.nonProxyHosts=\""
3853                                               + httpsProxy.getNonProxyHosts().replace( "|", "^|" ) + "\"" );
3854                 }
3855             }
3856         }
3857 
3858         if ( activeProxies.containsKey( "http" ) )
3859         {
3860             Proxy httpProxy = activeProxies.get( "http" );
3861             if ( StringUtils.isNotEmpty( httpProxy.getHost() ) )
3862             {
3863                 cmd.createArg().setValue( "-J-Dhttp.proxyHost=" + httpProxy.getHost() );
3864                 cmd.createArg().setValue( "-J-Dhttp.proxyPort=" + httpProxy.getPort() );
3865 
3866                 if ( !activeProxies.containsKey( "https" ) )
3867             {
3868                     cmd.createArg().setValue( "-J-Dhttps.proxyHost=" + httpProxy.getHost() );
3869                     cmd.createArg().setValue( "-J-Dhttps.proxyPort=" + httpProxy.getPort() );
3870                 }
3871 
3872                 if ( StringUtils.isNotEmpty( httpProxy.getNonProxyHosts() ) )
3873                 {
3874                     cmd.createArg().setValue( "-J-Dhttp.nonProxyHosts=\""
3875                                               + httpProxy.getNonProxyHosts().replace( "|", "^|" ) + "\"" );
3876                 }
3877             }
3878         }
3879 
3880         // We bravely ignore FTP because no one (probably) uses FTP for Javadoc
3881     }
3882 
3883     /**
3884      * Get the path of the Javadoc tool executable depending the user entry or try to find it depending the OS
3885      * or the <code>java.home</code> system property or the <code>JAVA_HOME</code> environment variable.
3886      *
3887      * @return the path of the Javadoc tool
3888      * @throws IOException if not found
3889      */
3890     private String getJavadocExecutable()
3891         throws IOException
3892     {
3893         Toolchain tc = getToolchain();
3894 
3895         if ( tc != null )
3896         {
3897             getLog().info( "Toolchain in maven-javadoc-plugin: " + tc );
3898             if ( javadocExecutable != null )
3899             {
3900                 getLog().warn( "Toolchains are ignored, 'javadocExecutable' parameter is set to " + javadocExecutable );
3901             }
3902             else
3903             {
3904                 javadocExecutable = tc.findTool( "javadoc" );
3905             }
3906         }
3907 
3908         String javadocCommand = "javadoc" + ( SystemUtils.IS_OS_WINDOWS ? ".exe" : "" );
3909 
3910         File javadocExe;
3911 
3912         // ----------------------------------------------------------------------
3913         // The javadoc executable is defined by the user
3914         // ----------------------------------------------------------------------
3915         if ( StringUtils.isNotEmpty( javadocExecutable ) )
3916         {
3917             javadocExe = new File( javadocExecutable );
3918 
3919             if ( javadocExe.isDirectory() )
3920             {
3921                 javadocExe = new File( javadocExe, javadocCommand );
3922             }
3923 
3924             if ( SystemUtils.IS_OS_WINDOWS && javadocExe.getName().indexOf( '.' ) < 0 )
3925             {
3926                 javadocExe = new File( javadocExe.getPath() + ".exe" );
3927             }
3928 
3929             if ( !javadocExe.isFile() )
3930             {
3931                 throw new IOException( "The javadoc executable '" + javadocExe
3932                     + "' doesn't exist or is not a file. Verify the <javadocExecutable/> parameter." );
3933             }
3934 
3935             return javadocExe.getAbsolutePath();
3936         }
3937         // CHECKSTYLE_OFF: LineLength
3938         // ----------------------------------------------------------------------
3939         // Try to find javadocExe from System.getProperty( "java.home" )
3940         // "java.home" is the "Installation directory for Java Runtime
3941         // Environment (JRE)" used to run this code. I.e. it is the parent of
3942         // the directory containing the `java` command used to start this
3943         // application. It does not necessarily have any relation to the
3944         // environment variable JAVA_HOME.
3945         //
3946         // In Java 8 and below the JRE is separate from the JDK. When
3947         // installing the JDK to my-dir/ the javadoc command is installed in
3948         // my-dir/bin/javadoc, the JRE is installed to my-dir/jre, and hence
3949         // the java command is installed to my-dir/jre/bin/java. In this
3950         // configuration "java.home" is mydir/jre/, threfore the relative path
3951         // to the javadoc command is ../bin/javadoc.
3952         //
3953         // In Java 9 and above the JRE is no longer in a subdirectory of the
3954         // JDK, i.e. the JDK and the JDK are merged. In this case the java
3955         // command is installed to my-dir/bin/java along side the javadoc
3956         // command. So the relative path from "java.home" to the javadoc
3957         // command is bin/javadoc.
3958         //
3959         // References
3960         //
3961         // "System Properties" in "The Java Tutorials"
3962         // https://docs.oracle.com/javase/tutorial/essential/environment/sysprop.html
3963         //
3964         // "Changes to the Installed JDK/JRE Image" in "JDK 9 Migration Guide"
3965         // https://docs.oracle.com/javase/9/migrate/toc.htm?xd_co_f=122a7174-9132-4acd-b122-fac02f8c4fef#JSMIG-GUID-D867DCCC-CEB5-4AFA-9D11-9C62B7A3FAB1
3966         //
3967         // "JEP 220: Modular Run-Time Images"
3968         // http://openjdk.java.net/jeps/220
3969         // ----------------------------------------------------------------------
3970         // For IBM's JDK 1.2
3971         // CHECKSTYLE_ON: LineLength
3972         if ( SystemUtils.IS_OS_AIX )
3973         {
3974             javadocExe =
3975                 new File( SystemUtils.getJavaHome() + File.separator + ".." + File.separator + "sh", javadocCommand );
3976         }
3977         // For Apple's JDK 1.6.x (and older?) on Mac OSX
3978         // CHECKSTYLE_OFF: MagicNumber
3979         else if ( SystemUtils.IS_OS_MAC_OSX && !JavaVersion.JAVA_SPECIFICATION_VERSION.isAtLeast( "1.7" ) )
3980         // CHECKSTYLE_ON: MagicNumber
3981         {
3982             javadocExe = new File( SystemUtils.getJavaHome() + File.separator + "bin", javadocCommand );
3983         }
3984         else if ( isJavaVersionAtLeast( org.apache.commons.lang3.JavaVersion.JAVA_9 ) )
3985         {
3986             javadocExe =
3987                 new File( SystemUtils.getJavaHome() + File.separator + "bin", javadocCommand );
3988         }
3989         else
3990         {
3991             // Java <= 8
3992             javadocExe =
3993                 new File( SystemUtils.getJavaHome() + File.separator + ".." + File.separator + "bin", javadocCommand );
3994         }
3995 
3996         // ----------------------------------------------------------------------
3997         // Try to find javadocExe from JAVA_HOME environment variable
3998         // ----------------------------------------------------------------------
3999         if ( !javadocExe.exists() || !javadocExe.isFile() )
4000         {
4001             Properties env = CommandLineUtils.getSystemEnvVars();
4002             String javaHome = env.getProperty( "JAVA_HOME" );
4003             if ( StringUtils.isEmpty( javaHome ) )
4004             {
4005                 throw new IOException( "The environment variable JAVA_HOME is not correctly set." );
4006             }
4007             if ( ( !new File( javaHome ).getCanonicalFile().exists() )
4008                 || ( new File( javaHome ).getCanonicalFile().isFile() ) )
4009             {
4010                 throw new IOException( "The environment variable JAVA_HOME=" + javaHome
4011                     + " doesn't exist or is not a valid directory." );
4012             }
4013 
4014             javadocExe = new File( javaHome + File.separator + "bin", javadocCommand );
4015         }
4016 
4017         if ( !javadocExe.getCanonicalFile().exists() || !javadocExe.getCanonicalFile().isFile() )
4018         {
4019             throw new IOException( "The javadoc executable '" + javadocExe
4020                 + "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable." );
4021         }
4022 
4023         return javadocExe.getAbsolutePath();
4024     }
4025 
4026     /**
4027      * Set a new value for <code>javadocRuntimeVersion</code>
4028      *
4029      * @param jExecutable not null
4030      * @throws MavenReportException if not found
4031      * @see JavadocUtil#getJavadocVersion(File)
4032      */
4033     private void setFJavadocVersion( File jExecutable )
4034         throws MavenReportException
4035     {
4036         JavaVersion jVersion;
4037         try
4038         {
4039             jVersion = JavadocUtil.getJavadocVersion( jExecutable );
4040         }
4041         catch ( IOException | CommandLineException | IllegalArgumentException e )
4042         {
4043             if ( getLog().isWarnEnabled() )
4044             {
4045                 getLog().warn( "Unable to find the javadoc version: " + e.getMessage() );
4046                 getLog().warn( "Using the Java version instead of, i.e. " + JAVA_VERSION );
4047             }
4048             jVersion = JAVA_VERSION;
4049         }
4050 
4051         if ( StringUtils.isNotEmpty( javadocVersion ) )
4052         {
4053             try
4054             {
4055                 javadocRuntimeVersion = JavaVersion.parse( javadocVersion );
4056             }
4057             catch ( NumberFormatException e )
4058             {
4059                 throw new MavenReportException( "Unable to parse javadoc version: " + e.getMessage(), e );
4060             }
4061 
4062             if ( javadocRuntimeVersion.compareTo( jVersion ) != 0 && getLog().isWarnEnabled() )
4063             {
4064                 getLog().warn( "Are you sure about the <javadocVersion/> parameter? It seems to be " + jVersion );
4065             }
4066         }
4067         else
4068         {
4069             javadocRuntimeVersion = jVersion;
4070         }
4071     }
4072 
4073     /**
4074      * Is the Javadoc version at least the requested version.
4075      *
4076      * @param requiredVersion the required version, for example 1.5f
4077      * @return <code>true</code> if the javadoc version is equal or greater than the
4078      *         required version
4079      */
4080     private boolean isJavaDocVersionAtLeast( JavaVersion requiredVersion )
4081     {
4082         return JAVA_VERSION.compareTo( requiredVersion ) >= 0;
4083     }
4084 
4085     /**
4086      * Convenience method to add an argument to the <code>command line</code>
4087      * conditionally based on the given flag.
4088      *
4089      * @param arguments a list of arguments, not null
4090      * @param b         the flag which controls if the argument is added or not.
4091      * @param value     the argument value to be added.
4092      */
4093     private void addArgIf( List<String> arguments, boolean b, String value )
4094     {
4095         if ( b )
4096         {
4097             arguments.add( value );
4098         }
4099     }
4100 
4101     /**
4102      * Convenience method to add an argument to the <code>command line</code>
4103      * regarding the requested Java version.
4104      *
4105      * @param arguments           a list of arguments, not null
4106      * @param b                   the flag which controls if the argument is added or not.
4107      * @param value               the argument value to be added.
4108      * @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
4109      * @see #addArgIf(List, boolean, String)
4110      * @see #isJavaDocVersionAtLeast(JavaVersion)
4111      */
4112     private void addArgIf( List<String> arguments, boolean b, String value, JavaVersion requiredJavaVersion )
4113     {
4114         if ( b )
4115         {
4116             if ( isJavaDocVersionAtLeast( requiredJavaVersion ) )
4117             {
4118                 addArgIf( arguments, true, value );
4119             }
4120             else
4121             {
4122                 if ( getLog().isWarnEnabled() )
4123                 {
4124                     getLog().warn( value + " option is not supported on Java version < " + requiredJavaVersion
4125                                        + ". Ignore this option." );
4126                 }
4127             }
4128         }
4129     }
4130 
4131     /**
4132      * Convenience method to add an argument to the <code>command line</code>
4133      * if the the value is not null or empty.
4134      * <p/>
4135      * Moreover, the value could be comma separated.
4136      *
4137      * @param arguments a list of arguments, not null
4138      * @param key       the argument name.
4139      * @param value     the argument value to be added.
4140      * @see #addArgIfNotEmpty(List, String, String, boolean)
4141      */
4142     private void addArgIfNotEmpty( List<String> arguments, String key, String value )
4143     {
4144         addArgIfNotEmpty( arguments, key, value, false );
4145     }
4146 
4147     /**
4148      * Convenience method to add an argument to the <code>command line</code>
4149      * if the the value is not null or empty.
4150      * <p/>
4151      * Moreover, the value could be comma separated.
4152      *
4153      * @param arguments           a list of arguments, not null
4154      * @param key                 the argument name.
4155      * @param value               the argument value to be added.
4156      * @param repeatKey           repeat or not the key in the command line
4157      * @param splitValue          if <code>true</code> given value will be tokenized by comma
4158      * @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
4159      * @see #addArgIfNotEmpty(List, String, String, boolean, boolean)
4160      * @see #isJavaDocVersionAtLeast(JavaVersion)
4161      */
4162     private void addArgIfNotEmpty( List<String> arguments, String key, String value, boolean repeatKey,
4163                                    boolean splitValue, JavaVersion requiredJavaVersion )
4164     {
4165         if ( StringUtils.isNotEmpty( value ) )
4166         {
4167             if ( isJavaDocVersionAtLeast( requiredJavaVersion ) )
4168             {
4169                 addArgIfNotEmpty( arguments, key, value, repeatKey, splitValue );
4170             }
4171             else
4172             {
4173                 if ( getLog().isWarnEnabled() )
4174                 {
4175                     getLog().warn( key + " option is not supported on Java version < " + requiredJavaVersion
4176                                        + ". Ignore this option." );
4177                 }
4178             }
4179         }
4180     }
4181 
4182     /**
4183      * Convenience method to add an argument to the <code>command line</code>
4184      * if the the value is not null or empty.
4185      * <p/>
4186      * Moreover, the value could be comma separated.
4187      *
4188      * @param arguments  a list of arguments, not null
4189      * @param key        the argument name.
4190      * @param value      the argument value to be added.
4191      * @param repeatKey  repeat or not the key in the command line
4192      * @param splitValue if <code>true</code> given value will be tokenized by comma
4193      */
4194     private void addArgIfNotEmpty( List<String> arguments, String key, String value, boolean repeatKey,
4195                                    boolean splitValue )
4196     {
4197         if ( StringUtils.isNotEmpty( value ) )
4198         {
4199             if ( StringUtils.isNotEmpty( key ) )
4200             {
4201                 arguments.add( key );
4202             }
4203 
4204             if ( splitValue )
4205             {
4206                 StringTokenizer token = new StringTokenizer( value, "," );
4207                 while ( token.hasMoreTokens() )
4208                 {
4209                     String current = token.nextToken().trim();
4210 
4211                     if ( StringUtils.isNotEmpty( current ) )
4212                     {
4213                         arguments.add( current );
4214 
4215                         if ( token.hasMoreTokens() && repeatKey )
4216                         {
4217                             arguments.add( key );
4218                         }
4219                     }
4220                 }
4221             }
4222             else
4223             {
4224                 arguments.add( value );
4225             }
4226         }
4227     }
4228 
4229     /**
4230      * Convenience method to add an argument to the <code>command line</code>
4231      * if the the value is not null or empty.
4232      * <p/>
4233      * Moreover, the value could be comma separated.
4234      *
4235      * @param arguments a list of arguments, not null
4236      * @param key       the argument name.
4237      * @param value     the argument value to be added.
4238      * @param repeatKey repeat or not the key in the command line
4239      */
4240     private void addArgIfNotEmpty( List<String> arguments, String key, String value, boolean repeatKey )
4241     {
4242         addArgIfNotEmpty( arguments, key, value, repeatKey, true );
4243     }
4244 
4245     /**
4246      * Convenience method to add an argument to the <code>command line</code>
4247      * regarding the requested Java version.
4248      *
4249      * @param arguments           a list of arguments, not null
4250      * @param key                 the argument name.
4251      * @param value               the argument value to be added.
4252      * @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
4253      * @see #addArgIfNotEmpty(List, String, String, JavaVersion, boolean)
4254      */
4255     private void addArgIfNotEmpty( List<String> arguments, String key, String value,
4256                                    JavaVersion requiredJavaVersion )
4257     {
4258         addArgIfNotEmpty( arguments, key, value, requiredJavaVersion, false );
4259     }
4260 
4261     /**
4262      * Convenience method to add an argument to the <code>command line</code>
4263      * regarding the requested Java version.
4264      *
4265      * @param arguments           a list of arguments, not null
4266      * @param key                 the argument name.
4267      * @param value               the argument value to be added.
4268      * @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
4269      * @param repeatKey           repeat or not the key in the command line
4270      * @see #addArgIfNotEmpty(List, String, String)
4271      * @see #isJavaDocVersionAtLeast
4272      */
4273     private void addArgIfNotEmpty( List<String> arguments, String key, String value, JavaVersion requiredJavaVersion,
4274                                    boolean repeatKey )
4275     {
4276         if ( StringUtils.isNotEmpty( value ) )
4277         {
4278             if ( isJavaDocVersionAtLeast( requiredJavaVersion ) )
4279             {
4280                 addArgIfNotEmpty( arguments, key, value, repeatKey );
4281             }
4282             else
4283             {
4284                 if ( getLog().isWarnEnabled() )
4285                 {
4286                     getLog().warn( key + " option is not supported on Java version < " + requiredJavaVersion );
4287                 }
4288             }
4289         }
4290     }
4291 
4292     /**
4293      * Convenience method to process {@link #offlineLinks} values as individual <code>-linkoffline</code>
4294      * javadoc options.
4295      * <br/>
4296      * If {@link #detectOfflineLinks}, try to add javadoc apidocs according Maven conventions for all modules given
4297      * in the project.
4298      *
4299      * @param arguments a list of arguments, not null
4300      * @throws MavenReportException if any
4301      * @see #offlineLinks
4302      * @see #getModulesLinks()
4303      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#package-list">package-list spec</a>
4304      */
4305     private void addLinkofflineArguments( List<String> arguments, Set<OfflineLink> offlineLinksList )
4306         throws MavenReportException
4307     {
4308         for ( OfflineLink offlineLink : offlineLinksList )
4309         {
4310             String url = offlineLink.getUrl();
4311             if ( StringUtils.isEmpty( url ) )
4312             {
4313                 continue;
4314             }
4315             url = cleanUrl( url );
4316 
4317             String location = offlineLink.getLocation();
4318             if ( StringUtils.isEmpty( location ) )
4319             {
4320                 continue;
4321             }
4322             if ( isValidJavadocLink( location, false ) )
4323             {
4324                 addArgIfNotEmpty( arguments, "-linkoffline",
4325                                   JavadocUtil.quotedPathArgument( url ) + " " + JavadocUtil.quotedPathArgument(
4326                                       location ), true );
4327             }
4328         }
4329     }
4330 
4331     private Set<OfflineLink> getLinkofflines() throws MavenReportException
4332     {
4333         Set<OfflineLink> offlineLinksList = collectOfflineLinks();
4334 
4335         offlineLinksList.addAll( getModulesLinks() );
4336 
4337         return offlineLinksList;
4338     }
4339 
4340     /**
4341      * Convenience method to process {@link #links} values as individual <code>-link</code> javadoc options.
4342      * If {@link #detectLinks}, try to add javadoc apidocs according Maven conventions for all dependencies given
4343      * in the project.
4344      * <br/>
4345      * According the Javadoc documentation, all defined link should have <code>${link}/package-list</code> fetchable.
4346      * <br/>
4347      * <b>Note</b>: when a link is not fetchable:
4348      * <ul>
4349      * <li>Javadoc 1.4 and less throw an exception</li>
4350      * <li>Javadoc 1.5 and more display a warning</li>
4351      * </ul>
4352      *
4353      * @param arguments a list of arguments, not null
4354      * @throws MavenReportException issue while generating report
4355      * @see #detectLinks
4356      * @see #getDependenciesLinks()
4357      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#package-list">package-list spec</a>
4358      */
4359     private void addLinkArguments( List<String> arguments )
4360         throws MavenReportException
4361     {
4362         Set<String> links = collectLinks();
4363 
4364         for ( String link : links )
4365         {
4366             if ( StringUtils.isEmpty( link ) )
4367             {
4368                 continue;
4369             }
4370 
4371             if ( isOffline && !link.startsWith( "file:" ) )
4372             {
4373                 continue;
4374             }
4375 
4376             while ( link.endsWith( "/" ) )
4377             {
4378                 link = link.substring( 0, link.lastIndexOf( "/" ) );
4379             }
4380 
4381             addArgIfNotEmpty( arguments, "-link", JavadocUtil.quotedPathArgument( link ), true, false );
4382         }
4383     }
4384 
4385     /**
4386      * Coppy all resources to the output directory
4387      *
4388      * @param javadocOutputDirectory not null
4389      * @throws MavenReportException if any
4390      * @see #copyDefaultStylesheet(File)
4391      * @see #copyJavadocResources(File)
4392      * @see #copyAdditionalJavadocResources(File)
4393      */
4394     private void copyAllResources( File javadocOutputDirectory )
4395         throws MavenReportException
4396     {
4397         // ----------------------------------------------------------------------
4398         // Copy default resources
4399         // ----------------------------------------------------------------------
4400 
4401         try
4402         {
4403             copyDefaultStylesheet( javadocOutputDirectory );
4404         }
4405         catch ( IOException e )
4406         {
4407             throw new MavenReportException( "Unable to copy default stylesheet: " + e.getMessage(), e );
4408         }
4409 
4410         // ----------------------------------------------------------------------
4411         // Copy javadoc resources
4412         // ----------------------------------------------------------------------
4413 
4414         if ( docfilessubdirs )
4415         {
4416             /*
4417              * Workaround since -docfilessubdirs doesn't seem to be used correctly by the javadoc tool
4418              * (see other note about -sourcepath). Take care of the -excludedocfilessubdir option.
4419              */
4420             try
4421             {
4422                 copyJavadocResources( javadocOutputDirectory );
4423             }
4424             catch ( IOException e )
4425             {
4426                 throw new MavenReportException( "Unable to copy javadoc resources: " + e.getMessage(), e );
4427             }
4428         }
4429 
4430         // ----------------------------------------------------------------------
4431         // Copy additional javadoc resources in artifacts
4432         // ----------------------------------------------------------------------
4433 
4434         copyAdditionalJavadocResources( javadocOutputDirectory );
4435     }
4436 
4437     /**
4438      * Copies the {@link #DEFAULT_CSS_NAME} css file from the current class
4439      * loader to the <code>outputDirectory</code> only if {@link #stylesheetfile} is empty and
4440      * {@link #stylesheet} is equals to <code>maven</code>.
4441      *
4442      * @param anOutputDirectory the output directory
4443      * @throws java.io.IOException if any
4444      * @see #DEFAULT_CSS_NAME
4445      * @see JavadocUtil#copyResource(java.net.URL, java.io.File)
4446      */
4447     private void copyDefaultStylesheet( File anOutputDirectory )
4448         throws IOException
4449     {
4450         if ( StringUtils.isNotEmpty( stylesheetfile ) )
4451         {
4452             return;
4453         }
4454 
4455         if ( !stylesheet.equalsIgnoreCase( "maven" ) )
4456         {
4457             return;
4458         }
4459 
4460         URL url = getClass().getClassLoader().getResource( RESOURCE_CSS_DIR + "/" + DEFAULT_CSS_NAME );
4461         File outFile = new File( anOutputDirectory, DEFAULT_CSS_NAME );
4462         JavadocUtil.copyResource( url, outFile );
4463     }
4464 
4465     /**
4466      * Method that copy all <code>doc-files</code> directories from <code>javadocDirectory</code> of
4467      * the current project or of the projects in the reactor to the <code>outputDirectory</code>.
4468      *
4469      * @param anOutputDirectory the output directory
4470      * @throws java.io.IOException if any
4471      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.2.html#docfiles">Reference
4472      *      Guide, Copies new "doc-files" directory for holding images and examples</a>
4473      * @see #docfilessubdirs
4474      */
4475     private void copyJavadocResources( File anOutputDirectory )
4476         throws IOException
4477     {
4478         if ( anOutputDirectory == null || !anOutputDirectory.exists() )
4479         {
4480             throw new IOException( "The outputDirectory " + anOutputDirectory + " doesn't exists." );
4481         }
4482 
4483         if ( includeDependencySources )
4484         {
4485             resolveDependencyBundles();
4486             if ( isNotEmpty( dependencyJavadocBundles ) )
4487             {
4488                 for ( JavadocBundle bundle : dependencyJavadocBundles )
4489                 {
4490                     File dir = bundle.getResourcesDirectory();
4491                     JavadocOptions options = bundle.getOptions();
4492                     if ( dir != null && dir.isDirectory() )
4493                     {
4494                         JavadocUtil.copyJavadocResources( anOutputDirectory, dir, options == null
4495                             ? null
4496                             : options.getExcludedDocfilesSubdirs() );
4497                     }
4498                 }
4499             }
4500         }
4501 
4502         if ( getJavadocDirectory() != null )
4503         {
4504             JavadocUtil.copyJavadocResources( anOutputDirectory, getJavadocDirectory(), excludedocfilessubdir );
4505         }
4506 
4507         if ( isAggregator() )
4508         {
4509             for ( MavenProject subProject : getAggregatedProjects()  )
4510             {
4511                 if ( subProject != project && getJavadocDirectory() != null )
4512                 {
4513                     String javadocDirRelative =
4514                         PathUtils.toRelative( project.getBasedir(), getJavadocDirectory().getAbsolutePath() );
4515                     File javadocDir = new File( subProject.getBasedir(), javadocDirRelative );
4516                     JavadocUtil.copyJavadocResources( anOutputDirectory, javadocDir, excludedocfilessubdir );
4517                 }
4518             }
4519         }
4520     }
4521 
4522     private synchronized void resolveDependencyBundles()
4523         throws IOException
4524     {
4525         if ( dependencyJavadocBundles == null )
4526         {
4527             dependencyJavadocBundles =
4528                 resourceResolver.resolveDependencyJavadocBundles( getDependencySourceResolverConfig() );
4529             if ( dependencyJavadocBundles == null )
4530             {
4531                 dependencyJavadocBundles = new ArrayList<>();
4532             }
4533         }
4534     }
4535 
4536     /**
4537      * Method that copy additional Javadoc resources from given artifacts.
4538      *
4539      * @param anOutputDirectory the output directory
4540      * @throws MavenReportException if any
4541      * @see #resourcesArtifacts
4542      */
4543     private void copyAdditionalJavadocResources( File anOutputDirectory )
4544         throws MavenReportException
4545     {
4546         Set<ResourcesArtifact> resourcesArtifacts = collectResourcesArtifacts();
4547         if ( isEmpty( resourcesArtifacts ) )
4548         {
4549             return;
4550         }
4551 
4552         UnArchiver unArchiver;
4553         try
4554         {
4555             unArchiver = archiverManager.getUnArchiver( "jar" );
4556         }
4557         catch ( NoSuchArchiverException e )
4558         {
4559             throw new MavenReportException(
4560                 "Unable to extract resources artifact. " + "No archiver for 'jar' available.", e );
4561         }
4562 
4563         for ( ResourcesArtifact item : resourcesArtifacts )
4564         {
4565             Artifact artifact;
4566             try
4567             {
4568                 artifact = createAndResolveArtifact( item );
4569             }
4570             catch ( ArtifactResolverException e )
4571             {
4572                 throw new MavenReportException( "Unable to resolve artifact:" + item, e );
4573             }
4574 
4575             unArchiver.setSourceFile( artifact.getFile() );
4576             unArchiver.setDestDirectory( anOutputDirectory );
4577             // remove the META-INF directory from resource artifact
4578             IncludeExcludeFileSelector[] selectors =
4579                 new IncludeExcludeFileSelector[]{ new IncludeExcludeFileSelector() };
4580             selectors[0].setExcludes( new String[]{ "META-INF/**" } );
4581             unArchiver.setFileSelectors( selectors );
4582 
4583             getLog().info( "Extracting contents of resources artifact: " + artifact.getArtifactId() );
4584             try
4585             {
4586                 unArchiver.extract();
4587             }
4588             catch ( ArchiverException e )
4589             {
4590                 throw new MavenReportException(
4591                     "Extraction of resources failed. Artifact that failed was: " + artifact.getArtifactId(), e );
4592             }
4593         }
4594     }
4595 
4596     /**
4597      * @param sourcePaths could be null
4598      * @return the list of package names for files in the sourcePaths
4599      */
4600     private List<String> getPackageNames( Map<Path, Collection<String>> sourcePaths )
4601     {
4602         List<String> returnList = new ArrayList<>();
4603 
4604         if ( !StringUtils.isEmpty( sourcepath ) )
4605         {
4606             return returnList;
4607         }
4608 
4609         for ( Entry<Path, Collection<String>> currentPathEntry : sourcePaths.entrySet() )
4610         {
4611             for ( String currentFile : currentPathEntry.getValue() )
4612             {
4613                 /*
4614                  * Remove the miscellaneous files
4615                  * https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
4616                  */
4617                 if ( currentFile.contains( "doc-files" ) )
4618                 {
4619                     continue;
4620                 }
4621 
4622                 int lastIndexOfSeparator = currentFile.lastIndexOf( "/" );
4623                 if ( lastIndexOfSeparator != -1 )
4624                 {
4625                     String packagename = currentFile.substring( 0, lastIndexOfSeparator ).replace( '/', '.' );
4626 
4627                     if ( !returnList.contains( packagename ) )
4628                     {
4629                         returnList.add( packagename );
4630                     }
4631                 }
4632             }
4633         }
4634 
4635         return returnList;
4636     }
4637 
4638     /**
4639      * @param allSourcePaths     not null, containing absolute and relative paths
4640      * @return a list of exported package names for files in allSourcePaths
4641      * @throws MavenReportException if any
4642      * @see #getFiles
4643      * @see #getSourcePaths()
4644      */
4645     private Collection<String> getPackageNamesRespectingJavaModules( Collection<JavadocModule> javadocModules )
4646             throws MavenReportException
4647     {
4648         if ( !StringUtils.isEmpty( sourcepath ) )
4649         {
4650             return Collections.emptyList();
4651         }
4652 
4653         Set<String> returnList = new LinkedHashSet<>();
4654         for ( JavadocModule javadocModule  : javadocModules )
4655         {
4656             Collection<Path> artifactSourcePaths = javadocModule.getSourcePaths();
4657             Set<String> exportedPackages = new HashSet<>();
4658             boolean exportAllPackages;
4659             ResolvePathResult resolvedPath = getResolvePathResult( javadocModule.getArtifactFile() );
4660             if ( resolvedPath != null && resolvedPath.getModuleNameSource() == ModuleNameSource.MODULEDESCRIPTOR )
4661             {
4662                 Set<JavaModuleDescriptor.JavaExports> exports = resolvedPath.getModuleDescriptor().exports();
4663                 if ( exports.isEmpty() )
4664                 {
4665                     continue;
4666                 }
4667                 for ( JavaModuleDescriptor.JavaExports export : exports )
4668                 {
4669                     exportedPackages.add( export.source() );
4670                 }
4671                 exportAllPackages = false;
4672             }
4673             else
4674             {
4675                 exportAllPackages = true;
4676             }
4677 
4678             for ( Map.Entry<Path, Collection<String>> currentPathEntry : getFiles( artifactSourcePaths ).entrySet() )
4679             {
4680                 for ( String currentFile : currentPathEntry.getValue() )
4681                 {
4682                     /*
4683                      * Remove the miscellaneous files
4684                      * https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
4685                      */
4686                     if ( currentFile.contains( "doc-files" ) )
4687                     {
4688                         continue;
4689                     }
4690 
4691                     int lastIndexOfSeparator = currentFile.lastIndexOf( File.separatorChar );
4692                     if ( lastIndexOfSeparator != -1 )
4693                     {
4694                         String packagename =
4695                             currentFile.substring( 0, lastIndexOfSeparator ).replace( File.separatorChar, '.' );
4696 
4697                         if ( exportAllPackages || exportedPackages.contains( packagename ) )
4698                         {
4699                             returnList.add( packagename );
4700                         }
4701                     }
4702                 }
4703             }
4704         }
4705 
4706         return returnList;
4707     }
4708 
4709     /**
4710      * @param sourcePaths could be null
4711      * @return a list files with unnamed package names for files in the sourcePaths
4712      */
4713     private List<String> getFilesWithUnnamedPackages( Map<Path, Collection<String>> sourcePaths )
4714     {
4715         List<String> returnList = new ArrayList<>();
4716 
4717         if ( !StringUtils.isEmpty( sourcepath ) )
4718         {
4719             return returnList;
4720         }
4721 
4722         for ( Entry<Path, Collection<String>> currentPathEntry : sourcePaths.entrySet() )
4723         {
4724             Path currentSourcePath = currentPathEntry.getKey();
4725 
4726             for ( String currentFile : currentPathEntry.getValue() )
4727             {
4728                 /*
4729                  * Remove the miscellaneous files
4730                  * https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
4731                  */
4732                 if ( currentFile.contains( "doc-files" ) )
4733                 {
4734                     continue;
4735                 }
4736 
4737                 if ( currentFile.indexOf( File.separatorChar ) == -1 )
4738                 {
4739                     returnList.add( currentSourcePath.resolve( currentFile ).toAbsolutePath().toString() );
4740                 }
4741             }
4742         }
4743 
4744         return returnList;
4745     }
4746 
4747     /**
4748      * Either return only the module descriptor or all sourcefiles per sourcepath
4749      * @param sourcePaths could be null
4750      * @return a list of files
4751      */
4752     private List<String> getSpecialFiles( Map<Path, Collection<String>> sourcePaths )
4753     {
4754         if ( !StringUtils.isEmpty( sourcepath ) )
4755         {
4756             return new ArrayList<>();
4757         }
4758 
4759         boolean containsModuleDescriptor = false;
4760         for ( Collection<String> sourcepathFiles : sourcePaths.values() )
4761         {
4762             containsModuleDescriptor = sourcepathFiles.contains( "module-info.java" );
4763             if ( containsModuleDescriptor )
4764             {
4765                 break;
4766             }
4767         }
4768 
4769         if ( containsModuleDescriptor )
4770         {
4771             return getModuleSourcePathFiles( sourcePaths );
4772         }
4773         else
4774         {
4775             return getFilesWithUnnamedPackages( sourcePaths );
4776         }
4777     }
4778 
4779     private List<String> getModuleSourcePathFiles( Map<Path, Collection<String>> sourcePaths )
4780     {
4781         List<String> returnList = new ArrayList<>();
4782 
4783         for ( Entry<Path, Collection<String>> currentPathEntry : sourcePaths.entrySet() )
4784         {
4785             Path currentSourcePath = currentPathEntry.getKey();
4786             if ( currentPathEntry.getValue().contains( "module-info.java" ) )
4787             {
4788                 returnList.add( currentSourcePath.resolve( "module-info.java" ).toAbsolutePath().toString() );
4789             }
4790             else
4791             {
4792                 for ( String currentFile : currentPathEntry.getValue() )
4793                 {
4794                     /*
4795                      * Remove the miscellaneous files
4796                      * https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
4797                      */
4798                     if ( currentFile.contains( "doc-files" ) )
4799                     {
4800                         continue;
4801                     }
4802 
4803                     returnList.add( currentSourcePath.resolve( currentFile ).toAbsolutePath().toString() );
4804                 }
4805             }
4806         }
4807         return returnList;
4808     }
4809 
4810     /**
4811      * Generate an <code>options</code> file for all options and arguments and add the <code>@options</code> in the
4812      * command line.
4813      *
4814      * @param cmd                    not null
4815      * @param arguments              not null
4816      * @param javadocOutputDirectory not null
4817      * @throws MavenReportException if any
4818      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#argumentfiles">
4819      *      Reference Guide, Command line argument files</a>
4820      * @see #OPTIONS_FILE_NAME
4821      */
4822     private void addCommandLineOptions( Commandline cmd, List<String> arguments, File javadocOutputDirectory )
4823         throws MavenReportException
4824     {
4825         File optionsFile = new File( javadocOutputDirectory, OPTIONS_FILE_NAME );
4826 
4827         StringBuilder options = new StringBuilder();
4828         options.append( StringUtils.join( arguments.iterator(),
4829                                           SystemUtils.LINE_SEPARATOR ) );
4830 
4831         /* default to platform encoding */
4832         String outputFileEncoding = null;
4833         if ( JAVA_VERSION.isAtLeast( "9" ) && JAVA_VERSION.isBefore( "12" ) )
4834         {
4835             outputFileEncoding = StandardCharsets.UTF_8.name();
4836         }
4837         try
4838         {
4839             FileUtils.fileWrite( optionsFile.getAbsolutePath(), outputFileEncoding, options.toString() );
4840         }
4841         catch ( IOException e )
4842         {
4843             throw new MavenReportException(
4844                 "Unable to write '" + optionsFile.getName() + "' temporary file for command execution", e );
4845         }
4846 
4847         cmd.createArg().setValue( "@" + OPTIONS_FILE_NAME );
4848     }
4849 
4850     /**
4851      * Generate a file called <code>argfile</code> (or <code>files</code>, depending the JDK) to hold files and add
4852      * the <code>@argfile</code> (or <code>@file</code>, depending the JDK) in the command line.
4853      *
4854      * @param cmd                    not null
4855      * @param javadocOutputDirectory not null
4856      * @param files                  not null
4857      * @throws MavenReportException if any
4858      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#argumentfiles">
4859      *      Reference Guide, Command line argument files
4860      *      </a>
4861      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#runningjavadoc">
4862      *      What s New in Javadoc 1.4
4863      *      </a>
4864      * @see #isJavaDocVersionAtLeast(JavaVersion)
4865      * @see #ARGFILE_FILE_NAME
4866      * @see #FILES_FILE_NAME
4867      */
4868     private void addCommandLineArgFile( Commandline cmd, File javadocOutputDirectory, List<String> files )
4869         throws MavenReportException
4870     {
4871         File argfileFile;
4872         if ( JAVA_VERSION.compareTo( SINCE_JAVADOC_1_4 ) >= 0 )
4873         {
4874             argfileFile = new File( javadocOutputDirectory, ARGFILE_FILE_NAME );
4875             cmd.createArg().setValue( "@" + ARGFILE_FILE_NAME );
4876         }
4877         else
4878         {
4879             argfileFile = new File( javadocOutputDirectory, FILES_FILE_NAME );
4880             cmd.createArg().setValue( "@" + FILES_FILE_NAME );
4881         }
4882 
4883         List<String> quotedFiles = new ArrayList<>( files.size() );
4884         for ( String file : files )
4885         {
4886             quotedFiles.add( JavadocUtil.quotedPathArgument( file ) );
4887         }
4888 
4889         try
4890         {
4891             FileUtils.fileWrite( argfileFile.getAbsolutePath(), null /* platform encoding */,
4892                                  StringUtils.join( quotedFiles.iterator(), SystemUtils.LINE_SEPARATOR ) );
4893         }
4894         catch ( IOException e )
4895         {
4896             throw new MavenReportException(
4897                 "Unable to write '" + argfileFile.getName() + "' temporary file for command execution", e );
4898         }
4899     }
4900 
4901     /**
4902      * Generate a file called <code>packages</code> to hold all package names and add the <code>@packages</code> in
4903      * the command line.
4904      *
4905      * @param cmd                    not null
4906      * @param javadocOutputDirectory not null
4907      * @param packageNames           not null
4908      * @throws MavenReportException if any
4909      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#argumentfiles">
4910      *      Reference Guide, Command line argument files</a>
4911      * @see #PACKAGES_FILE_NAME
4912      */
4913     private void addCommandLinePackages( Commandline cmd, File javadocOutputDirectory, Collection<String> packageNames )
4914         throws MavenReportException
4915     {
4916         File packagesFile = new File( javadocOutputDirectory, PACKAGES_FILE_NAME );
4917 
4918         try
4919         {
4920             FileUtils.fileWrite( packagesFile.getAbsolutePath(), null /* platform encoding */,
4921                                  StringUtils.join( packageNames.iterator(), SystemUtils.LINE_SEPARATOR ) );
4922         }
4923         catch ( IOException e )
4924         {
4925             throw new MavenReportException(
4926                 "Unable to write '" + packagesFile.getName() + "' temporary file for command execution", e );
4927         }
4928 
4929         cmd.createArg().setValue( "@" + PACKAGES_FILE_NAME );
4930     }
4931 
4932     /**
4933      * Checks for the validity of the Javadoc options used by the user.
4934      *
4935      * @throws MavenReportException if error
4936      */
4937     private void validateJavadocOptions()
4938         throws MavenReportException
4939     {
4940         // encoding
4941         if ( StringUtils.isNotEmpty( getEncoding() ) && !JavadocUtil.validateEncoding( getEncoding() ) )
4942         {
4943             throw new MavenReportException( "Unsupported option <encoding/> '" + getEncoding() + "'" );
4944         }
4945 
4946         // locale
4947         if ( StringUtils.isNotEmpty( this.locale ) )
4948         {
4949             StringTokenizer tokenizer = new StringTokenizer( this.locale, "_" );
4950             final int maxTokens = 3;
4951             if ( tokenizer.countTokens() > maxTokens )
4952             {
4953                 throw new MavenReportException(
4954                     "Unsupported option <locale/> '" + this.locale + "', should be language_country_variant." );
4955             }
4956 
4957             Locale localeObject = null;
4958             if ( tokenizer.hasMoreTokens() )
4959             {
4960                 String language = tokenizer.nextToken().toLowerCase( Locale.ENGLISH );
4961                 if ( !Arrays.asList( Locale.getISOLanguages() ).contains( language ) )
4962                 {
4963                     throw new MavenReportException(
4964                         "Unsupported language '" + language + "' in option <locale/> '" + this.locale + "'" );
4965                 }
4966                 localeObject = new Locale( language );
4967 
4968                 if ( tokenizer.hasMoreTokens() )
4969                 {
4970                     String country = tokenizer.nextToken().toUpperCase( Locale.ENGLISH );
4971                     if ( !Arrays.asList( Locale.getISOCountries() ).contains( country ) )
4972                     {
4973                         throw new MavenReportException(
4974                             "Unsupported country '" + country + "' in option <locale/> '" + this.locale + "'" );
4975                     }
4976                     localeObject = new Locale( language, country );
4977 
4978                     if ( tokenizer.hasMoreTokens() )
4979                     {
4980                         String variant = tokenizer.nextToken();
4981                         localeObject = new Locale( language, country, variant );
4982                     }
4983                 }
4984             }
4985 
4986             if ( localeObject == null )
4987             {
4988                 throw new MavenReportException(
4989                     "Unsupported option <locale/> '" + this.locale + "', should be language_country_variant." );
4990             }
4991 
4992             this.locale = localeObject.toString();
4993             final List<Locale> availableLocalesList = Arrays.asList( Locale.getAvailableLocales() );
4994             if ( StringUtils.isNotEmpty( localeObject.getVariant() ) && !availableLocalesList.contains( localeObject ) )
4995             {
4996                 StringBuilder sb = new StringBuilder();
4997                 sb.append( "Unsupported option <locale/> with variant '" ).append( this.locale );
4998                 sb.append( "'" );
4999 
5000                 localeObject = new Locale( localeObject.getLanguage(), localeObject.getCountry() );
5001                 this.locale = localeObject.toString();
5002 
5003                 sb.append( ", trying to use <locale/> without variant, i.e. '" ).append( this.locale ).append( "'" );
5004                 if ( getLog().isWarnEnabled() )
5005                 {
5006                     getLog().warn( sb.toString() );
5007                 }
5008             }
5009 
5010             if ( !availableLocalesList.contains( localeObject ) )
5011             {
5012                 throw new MavenReportException( "Unsupported option <locale/> '" + this.locale + "'" );
5013             }
5014         }
5015     }
5016 
5017     /**
5018      * Checks for the validity of the Standard Doclet options.
5019      * <br/>
5020      * For example, throw an exception if &lt;nohelp/&gt; and &lt;helpfile/&gt; options are used together.
5021      *
5022      * @throws MavenReportException if error or conflict found
5023      */
5024     private void validateStandardDocletOptions()
5025         throws MavenReportException
5026     {
5027         // docencoding
5028         if ( StringUtils.isNotEmpty( getDocencoding() ) && !JavadocUtil.validateEncoding( getDocencoding() ) )
5029         {
5030             throw new MavenReportException( "Unsupported option <docencoding/> '" + getDocencoding() + "'" );
5031         }
5032 
5033         // charset
5034         if ( StringUtils.isNotEmpty( getCharset() ) && !JavadocUtil.validateEncoding( getCharset() ) )
5035         {
5036             throw new MavenReportException( "Unsupported option <charset/> '" + getCharset() + "'" );
5037         }
5038 
5039         // helpfile
5040         if ( StringUtils.isNotEmpty( helpfile ) && nohelp )
5041         {
5042             throw new MavenReportException( "Option <nohelp/> conflicts with <helpfile/>" );
5043         }
5044 
5045         // overview
5046         if ( getOverview() != null && getOverview().exists() && nooverview )
5047         {
5048             throw new MavenReportException( "Option <nooverview/> conflicts with <overview/>" );
5049         }
5050 
5051         // index
5052         if ( splitindex && noindex )
5053         {
5054             throw new MavenReportException( "Option <noindex/> conflicts with <splitindex/>" );
5055         }
5056 
5057         // stylesheet
5058         if ( StringUtils.isNotEmpty( stylesheet ) && !( stylesheet.equalsIgnoreCase( "maven" )
5059             || stylesheet.equalsIgnoreCase( "java" ) ) )
5060         {
5061             throw new MavenReportException( "Option <stylesheet/> supports only \"maven\" or \"java\" value." );
5062         }
5063     }
5064 
5065     /**
5066      * Add Standard Javadoc Options.
5067      * <br/>
5068      * The <a href="package-summary.html#Standard_Javadoc_Options">package documentation</a> details the
5069      * Standard Javadoc Options wrapped by this Plugin.
5070      *
5071      * @param javadocOutputDirectory not null
5072      * @param arguments              not null
5073      * @param allSourcePaths         not null
5074      * @throws MavenReportException if any
5075      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadocoptions">https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadocoptions</a>
5076      */
5077     private void addJavadocOptions( File javadocOutputDirectory,
5078                                     List<String> arguments,
5079                                     Collection<JavadocModule> allSourcePaths,
5080                                     Set<OfflineLink> offlineLinks )
5081         throws MavenReportException
5082     {
5083         Collection<Path> sourcePaths = allSourcePaths.stream()
5084                                                      .flatMap( e -> e.getSourcePaths().stream() )
5085                                                      .collect( Collectors.toList() );
5086 
5087         validateJavadocOptions();
5088 
5089         // see com.sun.tools.javadoc.Start#parseAndExecute(String argv[])
5090         addArgIfNotEmpty( arguments, "-locale", JavadocUtil.quotedArgument( this.locale ) );
5091 
5092         // all options in alphabetical order
5093 
5094         if ( old && isJavaDocVersionAtLeast( SINCE_JAVADOC_1_4 ) )
5095         {
5096             if ( getLog().isWarnEnabled() )
5097             {
5098                 getLog().warn( "Javadoc 1.4+ doesn't support the -1.1 switch anymore. Ignore this option." );
5099             }
5100         }
5101         else
5102         {
5103             addArgIf( arguments, old, "-1.1" );
5104         }
5105 
5106         addArgIfNotEmpty( arguments, "-bootclasspath", JavadocUtil.quotedPathArgument( getBootclassPath() ) );
5107 
5108         if ( isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) )
5109         {
5110             addArgIf( arguments, breakiterator, "-breakiterator", SINCE_JAVADOC_1_5 );
5111         }
5112 
5113         List<MavenProject> aggregatedProjects = reactorProjects; // getAggregatedProjects();
5114         Map<String, MavenProject> reactorKeys = new HashMap<>( aggregatedProjects.size() );
5115         for ( MavenProject reactorProject : aggregatedProjects )
5116         {
5117             reactorKeys.put( ArtifactUtils.versionlessKey( reactorProject.getGroupId(),
5118                                                            reactorProject.getArtifactId() ), reactorProject );
5119         }
5120 
5121         Map<String, JavaModuleDescriptor> allModuleDescriptors = new HashMap<>();
5122 
5123         
5124         boolean supportModulePath = javadocRuntimeVersion.isAtLeast( "9" );
5125         if ( release != null ) 
5126         {
5127             supportModulePath &= JavaVersion.parse( release ).isAtLeast( "9" );
5128         }
5129         else if ( source != null )
5130         {
5131             supportModulePath &= JavaVersion.parse( source ).isAtLeast( "9" );
5132         }
5133 
5134         if ( supportModulePath )
5135         {
5136             for ( JavadocModule entry : allSourcePaths )
5137             {
5138                 File artifactFile = entry.getArtifactFile();
5139                 
5140                 ResolvePathResult resolvePathResult = getResolvePathResult( artifactFile );
5141 
5142                 if ( resolvePathResult == null || resolvePathResult.getModuleNameSource() == ModuleNameSource.FILENAME )
5143                 {
5144                     Path moduleDescriptor = findMainDescriptor( entry.getSourcePaths() );
5145 
5146                     if ( moduleDescriptor != null )
5147                     {
5148                         try
5149                         {
5150                             allModuleDescriptors.put( entry.getGa(),
5151                                       locationManager.parseModuleDescriptor( moduleDescriptor ).getModuleDescriptor() );
5152                         }
5153                         catch ( IOException e )
5154                         {
5155                             throw new MavenReportException( e.getMessage(), e );
5156                         }
5157                     }
5158                 }
5159                 else
5160                 {
5161                     allModuleDescriptors.put( entry.getGa(), resolvePathResult.getModuleDescriptor() );
5162                 }
5163             }
5164         }
5165 
5166         Collection<String> additionalModules = new ArrayList<>();
5167 
5168         ResolvePathResult mainResolvePathResult = null;
5169 
5170         Map<String, Collection<Path>> patchModules = new HashMap<>();
5171 
5172         Path moduleSourceDir = null;
5173         if ( supportModulePath && !allModuleDescriptors.isEmpty() )
5174         {
5175             Collection<String> unnamedProjects = new ArrayList<>();
5176             for ( JavadocModule javadocModule : allSourcePaths )
5177             {
5178                 MavenProject aggregatedProject = reactorKeys.get( javadocModule.getGa() );
5179                 if ( aggregatedProject != null && !"pom".equals( aggregatedProject.getPackaging() ) )
5180                 {
5181                     ResolvePathResult result = null;
5182 
5183                     // Prefer jar over outputDirectory, since it may may contain an automatic module name
5184                     File artifactFile = getArtifactFile( aggregatedProject );
5185                     if ( artifactFile != null )
5186                     {
5187                         ResolvePathRequest<File> request = ResolvePathRequest.ofFile( artifactFile );
5188                         try
5189                         {
5190                             result = locationManager.resolvePath( request );
5191                         }
5192                         catch ( RuntimeException e )
5193                         {
5194                             // most likely an invalid module name based on filename
5195                             if ( !"java.lang.module.FindException".equals( e.getClass().getName() ) )
5196                             {
5197                                 throw e;
5198                             }
5199                         }
5200                         catch ( IOException e )
5201                         {
5202                             throw new MavenReportException( e.getMessage(), e );
5203                         }
5204                     }
5205                     else
5206                     {
5207                         Path moduleDescriptor = findMainDescriptor( javadocModule.getSourcePaths() );
5208 
5209                         if ( moduleDescriptor != null )
5210                         {
5211                             try
5212                             {
5213                                 result = locationManager.parseModuleDescriptor( moduleDescriptor );
5214                             }
5215                             catch ( IOException e )
5216                             {
5217                                 throw new MavenReportException( e.getMessage(), e );
5218                             }
5219                         }
5220                     }
5221 
5222                     if ( result != null && result.getModuleDescriptor() != null )
5223                     {
5224                         moduleSourceDir = javadocOutputDirectory.toPath().resolve( "src" );
5225                         try
5226                         {
5227                             moduleSourceDir = Files.createDirectories( moduleSourceDir );
5228 
5229                             additionalModules.add( result.getModuleDescriptor().name() );
5230 
5231                             patchModules.put( result.getModuleDescriptor().name(), javadocModule.getSourcePaths() );
5232 
5233                             Path modulePath = moduleSourceDir.resolve( result.getModuleDescriptor().name() );
5234                             if ( !Files.isDirectory( modulePath ) )
5235                             {
5236                                 Files.createDirectory( modulePath );
5237                             }
5238                         }
5239                         catch ( IOException e )
5240                         {
5241                             throw new MavenReportException( e.getMessage(), e );
5242                         }
5243                     }
5244                     else
5245                     {
5246                         unnamedProjects.add( javadocModule.getGa() );
5247                     }
5248 
5249                     if ( aggregatedProject.equals( getProject() ) )
5250                     {
5251                         mainResolvePathResult = result;
5252                     }
5253                 }
5254                 else
5255                 {
5256                     // todo
5257                     getLog().error( "no reactor project: " + javadocModule.getGa() );
5258                 }
5259             }
5260 
5261             if ( !unnamedProjects.isEmpty() )
5262             {
5263                 getLog().error( "Creating an aggregated report for both named and unnamed modules is not possible." );
5264                 getLog().error( "Ensure that every module has a module descriptor or is a jar with a MANIFEST.MF "
5265                     + "containing an Automatic-Module-Name." );
5266                 getLog().error( "Fix the following projects:" );
5267                 for ( String unnamedProject : unnamedProjects )
5268                 {
5269                     getLog().error( " - " + unnamedProject );
5270                 }
5271                 throw new MavenReportException( "Aggregator report contains named and unnamed modules" );
5272             }
5273 
5274             if ( mainResolvePathResult != null
5275                 && ModuleNameSource.MANIFEST.equals( mainResolvePathResult.getModuleNameSource() ) )
5276             {
5277                 arguments.add( "--add-modules" );
5278                 arguments.add( "ALL-MODULE-PATH" );
5279             }
5280         }
5281 
5282         // MJAVADOC-506
5283         boolean moduleDescriptorSource = false;
5284         for ( Path sourcepath : sourcePaths )
5285         {
5286             if ( Files.isRegularFile( sourcepath.resolve( "module-info.java" ) ) )
5287             {
5288                 moduleDescriptorSource = true;
5289                 break;
5290             }
5291         }
5292 
5293         final ModuleNameSource mainModuleNameSource;
5294         if ( mainResolvePathResult != null )
5295         {
5296             mainModuleNameSource = mainResolvePathResult.getModuleNameSource();
5297         }
5298         else
5299         {
5300             mainModuleNameSource = null;
5301         }
5302 
5303         if ( supportModulePath
5304              && ( isAggregator()
5305                   || ModuleNameSource.MODULEDESCRIPTOR.equals( mainModuleNameSource )
5306                   || ModuleNameSource.MANIFEST.equals( mainModuleNameSource ) ) )
5307         {
5308             List<File> pathElements = new ArrayList<>( getPathElements() );
5309             File artifactFile = getArtifactFile( project );
5310             if ( artifactFile != null )
5311             {
5312                 pathElements.add( 0, artifactFile );
5313             }
5314 
5315             ResolvePathsRequest<File> request =
5316                 ResolvePathsRequest.ofFiles( pathElements );
5317 
5318             String mainModuleName = null;
5319             if ( mainResolvePathResult != null )
5320             {
5321                 request.setModuleDescriptor( mainResolvePathResult.getModuleDescriptor() );
5322                 mainModuleName = mainResolvePathResult.getModuleDescriptor().name();
5323             }
5324 
5325             request.setAdditionalModules( additionalModules );
5326             request.setIncludeStatic( isAggregator() );
5327 
5328             try
5329             {
5330                 ResolvePathsResult<File> result = locationManager.resolvePaths( request );
5331 
5332                 Set<File> modulePathElements = new HashSet<>( result.getModulepathElements().keySet() )  ;
5333 
5334                 Collection<File> classPathElements = new ArrayList<>( result.getClasspathElements().size() );
5335 
5336                 for ( File file : result.getClasspathElements() )
5337                 {
5338                     if ( file.isDirectory() && new File( file, "module-info.class" ).exists() )
5339                     {
5340                         modulePathElements.add( file );
5341                     }
5342                     else if ( ModuleNameSource.MANIFEST.equals( mainModuleNameSource ) )
5343                     {
5344                         ModuleNameSource depModuleNameSource =
5345                             locationManager.resolvePath( ResolvePathRequest.ofFile( file ) ).getModuleNameSource();
5346                         if ( ModuleNameSource.MODULEDESCRIPTOR.equals( depModuleNameSource )
5347                             || ModuleNameSource.MANIFEST.equals( depModuleNameSource ) )
5348                         {
5349                             modulePathElements.add( file );
5350                         }
5351                         else
5352                         {
5353                             patchModules.get( mainModuleName ).add( file.toPath() );
5354                         }
5355                     }
5356                     else
5357                     {
5358                         classPathElements.add( file );
5359                     }
5360                 }
5361 
5362                 /* MJAVADOC-620: also add all JARs where module-name-guessing leads to a FindException: */
5363                 for ( Entry<File, Exception> pathExceptionEntry : result.getPathExceptions().entrySet() )
5364                 {
5365                     Exception exception = pathExceptionEntry.getValue();
5366                     // For Java < 9 compatibility, reference FindException by name:
5367                     if ( "java.lang.module.FindException".equals( exception.getClass().getName() ) )
5368                     {
5369                         File jarPath = pathExceptionEntry.getKey();
5370                         classPathElements.add( jarPath );
5371                     }
5372                 }
5373 
5374                 String classpath = StringUtils.join( classPathElements.iterator(), File.pathSeparator );
5375                 addArgIfNotEmpty( arguments, "--class-path", JavadocUtil.quotedPathArgument( classpath ), false,
5376                                   false );
5377 
5378                 String modulepath =
5379                     StringUtils.join( modulePathElements.iterator(), File.pathSeparator );
5380                 addArgIfNotEmpty( arguments, "--module-path", JavadocUtil.quotedPathArgument( modulepath ), false,
5381                                   false );
5382             }
5383             catch ( IOException e )
5384             {
5385                 throw new MavenReportException( e.getMessage(), e );
5386             }
5387         }
5388         else if ( supportModulePath && moduleDescriptorSource && !isTest() )
5389         {
5390             String modulepath = StringUtils.join( getPathElements().iterator(), File.pathSeparator );
5391             addArgIfNotEmpty( arguments, "--module-path", JavadocUtil.quotedPathArgument( modulepath ) , false, false );
5392         }
5393         else
5394         {
5395             String classpath = StringUtils.join( getPathElements().iterator(), File.pathSeparator );
5396             addArgIfNotEmpty( arguments, "-classpath", JavadocUtil.quotedPathArgument( classpath ) , false, false );
5397         }
5398 
5399         for ( Entry<String, Collection<Path>> entry : patchModules.entrySet() )
5400         {
5401             addArgIfNotEmpty( arguments, "--patch-module", entry.getKey() + '='
5402                               + JavadocUtil.quotedPathArgument( getSourcePath( entry.getValue() ) ),
5403                                 false, false );
5404         }
5405 
5406         if ( StringUtils.isNotEmpty( doclet ) )
5407         {
5408             addArgIfNotEmpty( arguments, "-doclet", JavadocUtil.quotedArgument( doclet ) );
5409             addArgIfNotEmpty( arguments, "-docletpath", JavadocUtil.quotedPathArgument( getDocletPath() ) );
5410         }
5411 
5412         if ( StringUtils.isEmpty( encoding ) )
5413         {
5414             getLog().warn(
5415                 "Source files encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
5416                     + ", i.e. build is platform dependent!" );
5417         }
5418         addArgIfNotEmpty( arguments, "-encoding", JavadocUtil.quotedArgument( getEncoding() ) );
5419 
5420         addArgIfNotEmpty( arguments, "-extdirs",
5421                           JavadocUtil.quotedPathArgument( JavadocUtil.unifyPathSeparator( extdirs ) ) );
5422 
5423         if ( ( getOverview() != null ) && ( getOverview().exists() ) )
5424         {
5425             addArgIfNotEmpty( arguments, "-overview",
5426                               JavadocUtil.quotedPathArgument( getOverview().getAbsolutePath() ) );
5427         }
5428 
5429         arguments.add( getAccessLevel() );
5430 
5431         if ( isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) )
5432         {
5433             addArgIf( arguments, quiet, "-quiet", SINCE_JAVADOC_1_5 );
5434         }
5435 
5436         if ( release != null )
5437         {
5438             arguments.add( "--release" );
5439             arguments.add( release );
5440         }
5441         else
5442         {
5443             addArgIfNotEmpty( arguments, "-source", JavadocUtil.quotedArgument( source ), SINCE_JAVADOC_1_4 );
5444         }
5445 
5446         if ( ( StringUtils.isEmpty( sourcepath ) ) && ( StringUtils.isNotEmpty( subpackages ) ) )
5447         {
5448             sourcepath = StringUtils.join( sourcePaths.iterator(), File.pathSeparator );
5449         }
5450 
5451         if ( moduleSourceDir == null )
5452         {
5453             addArgIfNotEmpty( arguments, "-sourcepath",
5454                               JavadocUtil.quotedPathArgument( getSourcePath( sourcePaths ) ), false, false );
5455         }
5456         else if ( mainResolvePathResult == null
5457             || ModuleNameSource.MODULEDESCRIPTOR.equals( mainResolvePathResult.getModuleNameSource() ) )
5458         {
5459             addArgIfNotEmpty( arguments, "--module-source-path",
5460                               JavadocUtil.quotedPathArgument( moduleSourceDir.toString() ) );
5461         }
5462 
5463 
5464         if ( StringUtils.isNotEmpty( sourcepath ) && isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) )
5465         {
5466             addArgIfNotEmpty( arguments, "-subpackages", subpackages, SINCE_JAVADOC_1_5 );
5467         }
5468 
5469         // [MJAVADOC-497] must be after sourcepath is recalculated, since getExcludedPackages() depends on it
5470         addArgIfNotEmpty( arguments, "-exclude", getExcludedPackages( sourcePaths ), SINCE_JAVADOC_1_4 );
5471 
5472         addArgIf( arguments, verbose, "-verbose" );
5473 
5474         if ( additionalOptions != null && additionalOptions.length > 0 )
5475         {
5476             for ( String additionalOption : additionalOptions )
5477             {
5478                 arguments.add( additionalOption.replaceAll( "(?<!\\\\)\\\\(?!\\\\|:)", "\\\\" ) );
5479             }
5480         }
5481     }
5482 
5483     private ResolvePathResult getResolvePathResult( File artifactFile )
5484     {
5485         if ( artifactFile == null )
5486         {
5487             return null;
5488         }
5489 
5490         ResolvePathResult resolvePathResult = null;
5491         ResolvePathRequest<File> resolvePathRequest = ResolvePathRequest.ofFile( artifactFile );
5492         try
5493         {
5494             resolvePathResult = locationManager.resolvePath( resolvePathRequest );
5495 
5496             // happens when artifactFile is a directory without module descriptor
5497             if ( resolvePathResult.getModuleDescriptor() == null )
5498             {
5499                 return null;
5500             }
5501         }
5502         catch ( IOException | RuntimeException /* e.g java.lang.module.FindException */ e )
5503         {
5504             Throwable cause = e;
5505             while ( cause.getCause() != null )
5506             {
5507                 cause = cause.getCause();
5508             }
5509             getLog().warn( e.getMessage() );
5510         }
5511         return resolvePathResult;
5512     }
5513 
5514     private Path findMainDescriptor( Collection<Path> roots ) throws MavenReportException
5515     {
5516         for ( Map.Entry<Path, Collection<String>> entry : getFiles( roots ).entrySet() )
5517         {
5518             if ( entry.getValue().contains( "module-info.java" ) )
5519             {
5520                 return entry.getKey().resolve( "module-info.java" );
5521             }
5522         }
5523         return null;
5524     }
5525 
5526     /**
5527      * Add Standard Doclet Options.
5528      * <br/>
5529      * The <a href="package-summary.html#Standard_Doclet_Options">package documentation</a> details the
5530      * Standard Doclet Options wrapped by this Plugin.
5531      *
5532      * @param javadocOutputDirectory not null
5533      * @param arguments              not null
5534      * @throws MavenReportException if any
5535      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#standard">
5536      *      https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#standard</a>
5537      */
5538     private void addStandardDocletOptions( File javadocOutputDirectory,
5539                                            List<String> arguments,
5540                                            Set<OfflineLink> offlineLinks )
5541         throws MavenReportException
5542     {
5543         validateStandardDocletOptions();
5544 
5545         // all options in alphabetical order
5546 
5547         addArgIf( arguments, author, "-author" );
5548 
5549         addArgIfNotEmpty( arguments, "-bottom", JavadocUtil.quotedArgument( getBottomText() ), false, false );
5550 
5551         if ( !isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) )
5552         {
5553             addArgIf( arguments, breakiterator, "-breakiterator", SINCE_JAVADOC_1_4 );
5554         }
5555 
5556         addArgIfNotEmpty( arguments, "-charset", JavadocUtil.quotedArgument( getCharset() ) );
5557 
5558         addArgIfNotEmpty( arguments, "-d", JavadocUtil.quotedPathArgument( javadocOutputDirectory.toString() ) );
5559 
5560         addArgIfNotEmpty( arguments, "-docencoding", JavadocUtil.quotedArgument( getDocencoding() ) );
5561 
5562         addArgIf( arguments, docfilessubdirs, "-docfilessubdirs", SINCE_JAVADOC_1_4 );
5563 
5564         addArgIf( arguments, StringUtils.isNotEmpty( doclint ), "-Xdoclint:" + getDoclint(), SINCE_JAVADOC_1_8 );
5565 
5566         addArgIfNotEmpty( arguments, "-doctitle", JavadocUtil.quotedArgument( getDoctitle() ), false, false );
5567 
5568         if ( docfilessubdirs )
5569         {
5570             addArgIfNotEmpty( arguments, "-excludedocfilessubdir",
5571                               JavadocUtil.quotedPathArgument( excludedocfilessubdir ), SINCE_JAVADOC_1_4 );
5572         }
5573 
5574         addArgIfNotEmpty( arguments, "-footer", JavadocUtil.quotedArgument( footer ), false, false );
5575 
5576         addGroups( arguments );
5577 
5578         addArgIfNotEmpty( arguments, "-header", JavadocUtil.quotedArgument( header ), false, false );
5579 
5580         Optional<File> helpFile = getHelpFile( javadocOutputDirectory );
5581         if ( helpFile.isPresent() )
5582         {
5583             addArgIfNotEmpty( arguments, "-helpfile",
5584                               JavadocUtil.quotedPathArgument( helpFile.get().getAbsolutePath() ) );
5585         }
5586 
5587         addArgIf( arguments, keywords, "-keywords", SINCE_JAVADOC_1_4_2 );
5588 
5589         addLinkArguments( arguments );
5590 
5591         addLinkofflineArguments( arguments, offlineLinks );
5592 
5593         addArgIf( arguments, linksource, "-linksource", SINCE_JAVADOC_1_4 );
5594 
5595         if ( sourcetab > 0 )
5596         {
5597             if ( javadocRuntimeVersion == SINCE_JAVADOC_1_4_2 )
5598             {
5599                 addArgIfNotEmpty( arguments, "-linksourcetab", String.valueOf( sourcetab ) );
5600             }
5601             addArgIfNotEmpty( arguments, "-sourcetab", String.valueOf( sourcetab ), SINCE_JAVADOC_1_5 );
5602         }
5603 
5604         addArgIf( arguments, nocomment, "-nocomment", SINCE_JAVADOC_1_4 );
5605 
5606         addArgIf( arguments, nodeprecated, "-nodeprecated" );
5607 
5608         addArgIf( arguments, nodeprecatedlist, "-nodeprecatedlist" );
5609 
5610         addArgIf( arguments, nohelp, "-nohelp" );
5611 
5612         addArgIf( arguments, noindex, "-noindex" );
5613 
5614         addArgIf( arguments, nonavbar, "-nonavbar" );
5615 
5616         addArgIf( arguments, nooverview, "-nooverview" );
5617 
5618         addArgIfNotEmpty( arguments, "-noqualifier", JavadocUtil.quotedArgument( noqualifier ), SINCE_JAVADOC_1_4 );
5619 
5620         addArgIf( arguments, nosince, "-nosince" );
5621 
5622         addArgIf( arguments, notimestamp, "-notimestamp", SINCE_JAVADOC_1_5 );
5623 
5624         addArgIf( arguments, notree, "-notree" );
5625 
5626         addArgIfNotEmpty( arguments, "-packagesheader", JavadocUtil.quotedArgument( packagesheader ),
5627                           SINCE_JAVADOC_1_4_2 );
5628 
5629         if ( !isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) ) // Sun bug: 4714350
5630         {
5631             addArgIf( arguments, quiet, "-quiet", SINCE_JAVADOC_1_4 );
5632         }
5633 
5634         addArgIf( arguments, serialwarn, "-serialwarn" );
5635 
5636         addArgIf( arguments, splitindex, "-splitindex" );
5637 
5638         Optional<File> stylesheetfile = getStylesheetFile( javadocOutputDirectory );
5639         
5640         if ( stylesheetfile.isPresent() )
5641         {
5642             addArgIfNotEmpty( arguments, "-stylesheetfile",
5643                               JavadocUtil.quotedPathArgument( stylesheetfile.get().getAbsolutePath() ) );
5644         }
5645         
5646         addAddStyleSheets( arguments ); 
5647 
5648         if ( StringUtils.isNotEmpty( sourcepath ) && !isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) )
5649         {
5650             addArgIfNotEmpty( arguments, "-subpackages", subpackages, SINCE_JAVADOC_1_4 );
5651         }
5652 
5653         addArgIfNotEmpty( arguments, "-taglet", JavadocUtil.quotedArgument( taglet ), SINCE_JAVADOC_1_4 );
5654         addTaglets( arguments );
5655         addTagletsFromTagletArtifacts( arguments );
5656         addArgIfNotEmpty( arguments, "-tagletpath", JavadocUtil.quotedPathArgument( getTagletPath() ),
5657                           SINCE_JAVADOC_1_4 );
5658 
5659         addTags( arguments );
5660 
5661         addArgIfNotEmpty( arguments, "-top", JavadocUtil.quotedArgument( top ), false, false, SINCE_JAVADOC_1_6 );
5662 
5663         addArgIf( arguments, use, "-use" );
5664 
5665         addArgIf( arguments, version, "-version" );
5666 
5667         addArgIfNotEmpty( arguments, "-windowtitle", JavadocUtil.quotedArgument( getWindowtitle() ), false, false );
5668     }
5669 
5670     /**
5671      * Add <code>groups</code> parameter to arguments.
5672      *
5673      * @param arguments not null
5674      * @throws MavenReportException
5675      */
5676     private void addGroups( List<String> arguments )
5677         throws MavenReportException
5678     {
5679         Set<Group> groups = collectGroups();
5680         if ( isEmpty( groups ) )
5681         {
5682             return;
5683         }
5684 
5685         for ( Group group : groups )
5686         {
5687             if ( group == null || StringUtils.isEmpty( group.getTitle() ) || StringUtils.isEmpty(
5688                 group.getPackages() ) )
5689             {
5690                 if ( getLog().isWarnEnabled() )
5691                 {
5692                     getLog().warn( "A group option is empty. Ignore this option." );
5693                 }
5694             }
5695             else
5696             {
5697                 String groupTitle = StringUtils.replace( group.getTitle(), ",", "&#44;" );
5698                 addArgIfNotEmpty( arguments, "-group",
5699                                   JavadocUtil.quotedArgument( groupTitle ) + " " + JavadocUtil.quotedArgument(
5700                                       group.getPackages() ), true );
5701             }
5702         }
5703     }
5704 
5705     /**
5706      * Add <code>tags</code> parameter to arguments.
5707      *
5708      * @param arguments not null
5709      * @throws MavenReportException
5710      */
5711     private void addTags( List<String> arguments )
5712         throws MavenReportException
5713     {
5714         final String lineSeparator;
5715         if ( javadocRuntimeVersion.isBefore( "9" ) )
5716         {
5717             lineSeparator = " ";
5718         }
5719         else
5720         {
5721             lineSeparator = " \\\\" + SystemUtils.LINE_SEPARATOR;
5722         }
5723 
5724         for ( Tag tag : collectTags() )
5725         {
5726             if ( StringUtils.isEmpty( tag.getName() ) )
5727             {
5728                 if ( getLog().isWarnEnabled() )
5729                 {
5730                     getLog().warn( "A tag name is empty. Ignore this option." );
5731                 }
5732             }
5733             else
5734             {
5735                 String value = "\"" + tag.getName();
5736                 if ( StringUtils.isNotEmpty( tag.getPlacement() ) )
5737                 {
5738                     value += ":" + tag.getPlacement().replaceAll( "\\R", lineSeparator );
5739                     if ( StringUtils.isNotEmpty( tag.getHead() ) )
5740                     {
5741                         value += ":" + tag.getHead().replaceAll( "\\R", lineSeparator );
5742                     }
5743                 }
5744                 value += "\"";
5745                 addArgIfNotEmpty( arguments, "-tag", value, SINCE_JAVADOC_1_4 );
5746             }
5747         }
5748     }
5749 
5750     /**
5751      * Add <code>taglets</code> parameter to arguments.
5752      *
5753      * @param arguments not null
5754      */
5755     private void addTaglets( List<String> arguments )
5756     {
5757         if ( taglets == null )
5758         {
5759             return;
5760         }
5761 
5762         for ( Taglet taglet1 : taglets )
5763         {
5764             if ( ( taglet1 == null ) || ( StringUtils.isEmpty( taglet1.getTagletClass() ) ) )
5765             {
5766                 if ( getLog().isWarnEnabled() )
5767                 {
5768                     getLog().warn( "A taglet option is empty. Ignore this option." );
5769                 }
5770             }
5771             else
5772             {
5773                 addArgIfNotEmpty( arguments, "-taglet", JavadocUtil.quotedArgument( taglet1.getTagletClass() ),
5774                                   SINCE_JAVADOC_1_4 );
5775             }
5776         }
5777     }
5778 
5779     /**
5780      * Auto-detect taglets class name from <code>tagletArtifacts</code> and add them to arguments.
5781      *
5782      * @param arguments not null
5783      * @throws MavenReportException if any
5784      * @see JavadocUtil#getTagletClassNames(File)
5785      */
5786     private void addTagletsFromTagletArtifacts( List<String> arguments )
5787         throws MavenReportException
5788     {
5789         Set<TagletArtifact> tArtifacts = new LinkedHashSet<>();
5790         if ( tagletArtifacts != null && tagletArtifacts.length > 0 )
5791         {
5792             tArtifacts.addAll( Arrays.asList( tagletArtifacts ) );
5793         }
5794 
5795         if ( includeDependencySources )
5796         {
5797             try
5798             {
5799                 resolveDependencyBundles();
5800             }
5801             catch ( IOException e )
5802             {
5803                 throw new MavenReportException(
5804                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
5805             }
5806 
5807             if ( isNotEmpty( dependencyJavadocBundles ) )
5808             {
5809                 for ( JavadocBundle bundle : dependencyJavadocBundles )
5810                 {
5811                     JavadocOptions options = bundle.getOptions();
5812                     if ( options != null && isNotEmpty( options.getTagletArtifacts() ) )
5813                     {
5814                         tArtifacts.addAll( options.getTagletArtifacts() );
5815                     }
5816                 }
5817             }
5818         }
5819 
5820         if ( isEmpty( tArtifacts ) )
5821         {
5822             return;
5823         }
5824 
5825         List<String> tagletsPath = new ArrayList<>();
5826 
5827         for ( TagletArtifact aTagletArtifact : tArtifacts )
5828         {
5829             if ( ( StringUtils.isNotEmpty( aTagletArtifact.getGroupId() ) ) && ( StringUtils.isNotEmpty(
5830                 aTagletArtifact.getArtifactId() ) ) && ( StringUtils.isNotEmpty( aTagletArtifact.getVersion() ) ) )
5831             {
5832                 Artifact artifact;
5833                 try
5834                 {
5835                     artifact = createAndResolveArtifact( aTagletArtifact );
5836                 }
5837                 catch ( ArtifactResolverException e )
5838                 {
5839                     throw new MavenReportException( "Unable to resolve artifact:" + aTagletArtifact, e );
5840                 }
5841 
5842                 tagletsPath.add( artifact.getFile().getAbsolutePath() );
5843             }
5844         }
5845 
5846         tagletsPath = JavadocUtil.pruneFiles( tagletsPath );
5847 
5848         for ( String tagletJar : tagletsPath )
5849         {
5850             if ( !tagletJar.toLowerCase( Locale.ENGLISH ).endsWith( ".jar" ) )
5851             {
5852                 continue;
5853             }
5854 
5855             List<String> tagletClasses;
5856             try
5857             {
5858                 tagletClasses = JavadocUtil.getTagletClassNames( new File( tagletJar ) );
5859             }
5860             catch ( IOException e )
5861             {
5862                 if ( getLog().isWarnEnabled() )
5863                 {
5864                     getLog().warn( "Unable to auto-detect Taglet class names from '" + tagletJar
5865                                        + "'. Try to specify them with <taglets/>." );
5866                 }
5867                 if ( getLog().isDebugEnabled() )
5868                 {
5869                     getLog().debug( "IOException: " + e.getMessage(), e );
5870                 }
5871                 continue;
5872             }
5873             catch ( ClassNotFoundException e )
5874             {
5875                 if ( getLog().isWarnEnabled() )
5876                 {
5877                     getLog().warn( "Unable to auto-detect Taglet class names from '" + tagletJar
5878                                        + "'. Try to specify them with <taglets/>." );
5879                 }
5880                 if ( getLog().isDebugEnabled() )
5881                 {
5882                     getLog().debug( "ClassNotFoundException: " + e.getMessage(), e );
5883                 }
5884                 continue;
5885             }
5886             catch ( NoClassDefFoundError e )
5887             {
5888                 if ( getLog().isWarnEnabled() )
5889                 {
5890                     getLog().warn( "Unable to auto-detect Taglet class names from '" + tagletJar
5891                                        + "'. Try to specify them with <taglets/>." );
5892                 }
5893                 if ( getLog().isDebugEnabled() )
5894                 {
5895                     getLog().debug( "NoClassDefFoundError: " + e.getMessage(), e );
5896                 }
5897                 continue;
5898             }
5899 
5900             if ( tagletClasses != null && !tagletClasses.isEmpty() )
5901             {
5902                 for ( String tagletClass : tagletClasses )
5903                 {
5904                     addArgIfNotEmpty( arguments, "-taglet", JavadocUtil.quotedArgument( tagletClass ),
5905                                       SINCE_JAVADOC_1_4 );
5906                 }
5907             }
5908         }
5909     }
5910 
5911     /**
5912      * Execute the Javadoc command line
5913      *
5914      * @param cmd                    not null
5915      * @param javadocOutputDirectory not null
5916      * @throws MavenReportException if any errors occur
5917      */
5918     private void executeJavadocCommandLine( Commandline cmd, File javadocOutputDirectory )
5919             throws MavenReportException
5920     {
5921         if ( staleDataPath != null )
5922         {
5923             if ( !isUpToDate( cmd ) )
5924             {
5925                 doExecuteJavadocCommandLine( cmd, javadocOutputDirectory );
5926                 StaleHelper.writeStaleData( cmd, staleDataPath.toPath() );
5927             }
5928         }
5929         else
5930         {
5931             doExecuteJavadocCommandLine( cmd, javadocOutputDirectory );
5932         }
5933     }
5934 
5935     /**
5936      * Check if the javadoc is uptodate or not
5937      *
5938      * @param cmd                    not null
5939      * @return <code>true</code> is the javadoc is uptodate, <code>false</code> otherwise
5940      * @throws MavenReportException  if any error occur
5941      */
5942     private boolean isUpToDate( Commandline cmd )
5943             throws MavenReportException
5944     {
5945         try
5946         {
5947             String curdata = StaleHelper.getStaleData( cmd );
5948             Path cacheData = staleDataPath.toPath();
5949             String prvdata;
5950             if ( Files.isRegularFile( cacheData ) )
5951             {
5952                 prvdata = new String( Files.readAllBytes( cacheData ), StandardCharsets.UTF_8 );
5953             }
5954             else
5955             {
5956                 prvdata = null;
5957             }
5958             if ( curdata.equals( prvdata ) )
5959             {
5960                 getLog().info( "Skipping javadoc generation, everything is up to date." );
5961                 return true;
5962             }
5963             else
5964             {
5965                 if ( prvdata == null )
5966                 {
5967                     getLog().info( "No previous run data found, generating javadoc." );
5968                 }
5969                 else
5970                 {
5971                     getLog().info( "Configuration changed, re-generating javadoc." );
5972                 }
5973             }
5974         }
5975         catch ( IOException e )
5976         {
5977             throw new MavenReportException( "Error checking uptodate status", e );
5978         }
5979         return false;
5980     }
5981 
5982     /**
5983      * Execute the Javadoc command line
5984      *
5985      * @param cmd                    not null
5986      * @param javadocOutputDirectory not null
5987      * @throws MavenReportException if any errors occur
5988      */
5989     private void doExecuteJavadocCommandLine( Commandline cmd, File javadocOutputDirectory )
5990         throws MavenReportException
5991     {
5992         if ( getLog().isDebugEnabled() )
5993         {
5994             // no quoted arguments
5995             getLog().debug( CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" ) );
5996         }
5997 
5998         String cmdLine = null;
5999         if ( debug )
6000         {
6001             cmdLine = CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" );
6002 
6003             writeDebugJavadocScript( cmdLine, javadocOutputDirectory );
6004         }
6005 
6006         CommandLineUtils.StringStreamConsumer err = new JavadocUtil.JavadocOutputStreamConsumer();
6007         CommandLineUtils.StringStreamConsumer out = new JavadocUtil.JavadocOutputStreamConsumer();
6008         try
6009         {
6010             int exitCode = CommandLineUtils.executeCommandLine( cmd, out, err );
6011 
6012             String output = ( StringUtils.isEmpty( out.getOutput() ) ? null : '\n' + out.getOutput().trim() );
6013 
6014             if ( exitCode != 0 )
6015             {
6016                 if ( cmdLine == null )
6017                 {
6018                     cmdLine = CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" );
6019                 }
6020                 writeDebugJavadocScript( cmdLine, javadocOutputDirectory );
6021 
6022                 if ( StringUtils.isNotEmpty( output ) && StringUtils.isEmpty( err.getOutput() )
6023                     && isJavadocVMInitError( output ) )
6024                 {
6025                     throw new MavenReportException( output + '\n' + '\n' + JavadocUtil.ERROR_INIT_VM + '\n'
6026                         + "Or, try to reduce the Java heap size for the Javadoc goal using "
6027                         + "-Dminmemory=<size> and -Dmaxmemory=<size>." + '\n' + '\n' + "Command line was: " + cmdLine
6028                         + '\n' + '\n' + "Refer to the generated Javadoc files in '" + javadocOutputDirectory
6029                         + "' dir.\n" );
6030                 }
6031 
6032                 if ( StringUtils.isNotEmpty( output ) )
6033                 {
6034                     getLog().info( output );
6035                 }
6036 
6037                 StringBuilder msg = new StringBuilder( "\nExit code: " );
6038                 msg.append( exitCode );
6039                 if ( StringUtils.isNotEmpty( err.getOutput() ) )
6040                 {
6041                     msg.append( " - " ).append( err.getOutput() );
6042                 }
6043                 msg.append( '\n' );
6044                 msg.append( "Command line was: " ).append( cmdLine ).append( '\n' ).append( '\n' );
6045 
6046                 msg.append( "Refer to the generated Javadoc files in '" ).append( javadocOutputDirectory )
6047                 .append( "' dir.\n" );
6048 
6049                 throw new MavenReportException( msg.toString() );
6050             }
6051 
6052             if ( StringUtils.isNotEmpty( output ) )
6053             {
6054                 getLog().info( output );
6055             }
6056         }
6057         catch ( CommandLineException e )
6058         {
6059             throw new MavenReportException( "Unable to execute javadoc command: " + e.getMessage(), e );
6060         }
6061 
6062         // ----------------------------------------------------------------------
6063         // Handle Javadoc warnings
6064         // ----------------------------------------------------------------------
6065 
6066         if ( StringUtils.isNotEmpty( err.getOutput() ) && getLog().isWarnEnabled() )
6067         {
6068             getLog().warn( "Javadoc Warnings" );
6069 
6070             StringTokenizer token = new StringTokenizer( err.getOutput(), "\n" );
6071             while ( token.hasMoreTokens() )
6072             {
6073                 String current = token.nextToken().trim();
6074 
6075                 getLog().warn( current );
6076             }
6077         }
6078 
6079         if ( StringUtils.isNotEmpty( err.getOutput() ) && failOnWarnings )
6080         {
6081             throw new MavenReportException( "Project contains Javadoc Warnings" );
6082         }
6083     }
6084 
6085     /**
6086      * Patches the given Javadoc output directory to work around CVE-2013-1571
6087      * (see http://www.kb.cert.org/vuls/id/225657).
6088      *
6089      * @param javadocOutputDirectory directory to scan for vulnerabilities
6090      * @param outputEncoding         encoding used by the javadoc tool (-docencoding parameter).
6091      *                               If {@code null}, the platform's default encoding is used (like javadoc does).
6092      * @return the number of patched files
6093      */
6094     private int fixFrameInjectionBug( File javadocOutputDirectory, String outputEncoding )
6095         throws IOException
6096     {
6097         final String fixData;
6098 
6099         try ( InputStream in = this.getClass().getResourceAsStream( "frame-injection-fix.txt" ) )
6100         {
6101             if ( in == null )
6102             {
6103                 throw new FileNotFoundException( "Missing resource 'frame-injection-fix.txt' in classpath." );
6104             }
6105             fixData = org.codehaus.plexus.util.StringUtils
6106                 .unifyLineSeparators( IOUtil.toString( in, "US-ASCII" ) ).trim();
6107         }
6108 
6109         final DirectoryScanner ds = new DirectoryScanner();
6110         ds.setBasedir( javadocOutputDirectory );
6111         ds.setCaseSensitive( false );
6112         ds.setIncludes( new String[]{ "**/index.html", "**/index.htm", "**/toc.html", "**/toc.htm" } );
6113         ds.addDefaultExcludes();
6114         ds.scan();
6115         int patched = 0;
6116         for ( String f : ds.getIncludedFiles() )
6117         {
6118             final File file = new File( javadocOutputDirectory, f );
6119             // we load the whole file as one String (toc/index files are
6120             // generally small, because they only contain frameset declaration):
6121             final String fileContents = FileUtils.fileRead( file, outputEncoding );
6122             // check if file may be vulnerable because it was not patched with "validURL(url)":
6123             if ( !StringUtils.contains( fileContents, "function validURL(url) {" ) )
6124             {
6125                 // we need to patch the file!
6126                 final String patchedFileContents =
6127                     StringUtils.replaceOnce( fileContents, "function loadFrames() {", fixData );
6128                 if ( !patchedFileContents.equals( fileContents ) )
6129                 {
6130                     FileUtils.fileWrite( file, outputEncoding, patchedFileContents );
6131                     patched++;
6132                 }
6133             }
6134         }
6135         return patched;
6136     }
6137 
6138     /**
6139      * @param outputFile        not nul
6140      * @param inputResourceName a not null resource in <code>src/main/java</code>, <code>src/main/resources</code> or
6141      *                          <code>src/main/javadoc</code> or in the Javadoc plugin dependencies.
6142      * @return the resource file absolute path as String
6143      * @since 2.6
6144      */
6145     private Optional<File> getResource( File outputFile, String inputResourceName )
6146     {
6147         if ( inputResourceName.startsWith( "/" ) )
6148         {
6149             inputResourceName = inputResourceName.replaceFirst( "//*", "" );
6150         }
6151 
6152         List<String> classPath = new ArrayList<>();
6153         classPath.add( project.getBuild().getSourceDirectory() );
6154 
6155         URL resourceURL = getResource( classPath, inputResourceName );
6156         if ( resourceURL != null )
6157         {
6158             getLog().debug( inputResourceName + " found in the main src directory of the project." );
6159             return Optional.of( FileUtils.toFile( resourceURL ) );
6160         }
6161 
6162         classPath.clear();
6163         List<Resource> resources = project.getBuild().getResources();
6164         for ( Resource resource : resources )
6165         {
6166             classPath.add( resource.getDirectory() );
6167         }
6168         resourceURL = getResource( classPath, inputResourceName );
6169         if ( resourceURL != null )
6170         {
6171             getLog().debug( inputResourceName + " found in the main resources directories of the project." );
6172             return Optional.of( FileUtils.toFile( resourceURL ) );
6173         }
6174 
6175         if ( javadocDirectory.exists() )
6176         {
6177             classPath.clear();
6178             classPath.add( javadocDirectory.getAbsolutePath() );
6179             resourceURL = getResource( classPath, inputResourceName );
6180             if ( resourceURL != null )
6181             {
6182                 getLog().debug( inputResourceName + " found in the main javadoc directory of the project." );
6183                 return Optional.of( FileUtils.toFile( resourceURL ) );
6184             }
6185         }
6186 
6187         classPath.clear();
6188         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
6189         Plugin javadocPlugin = getPlugin( project, pluginId );
6190         if ( javadocPlugin != null && javadocPlugin.getDependencies() != null )
6191         {
6192             List<Dependency> dependencies = javadocPlugin.getDependencies();
6193             for ( Dependency dependency : dependencies )
6194             {
6195                 JavadocPathArtifact javadocPathArtifact = new JavadocPathArtifact();
6196                 javadocPathArtifact.setGroupId( dependency.getGroupId() );
6197                 javadocPathArtifact.setArtifactId( dependency.getArtifactId() );
6198                 javadocPathArtifact.setVersion( dependency.getVersion() );
6199                 Artifact artifact = null;
6200                 try
6201                 {
6202                     artifact = createAndResolveArtifact( javadocPathArtifact );
6203                 }
6204                 catch ( Exception e )
6205                 {
6206                     logError( "Unable to retrieve the dependency: " + dependency + ". Ignored.", e );
6207                 }
6208 
6209                 if ( artifact != null && artifact.getFile().exists() )
6210                 {
6211                     classPath.add( artifact.getFile().getAbsolutePath() );
6212                 }
6213             }
6214             resourceURL = getResource( classPath, inputResourceName );
6215             if ( resourceURL != null )
6216             {
6217                 getLog().debug( inputResourceName + " found in javadoc plugin dependencies." );
6218                 try
6219                 {
6220                     JavadocUtil.copyResource( resourceURL, outputFile );
6221 
6222                     return Optional.of( outputFile );
6223                 }
6224                 catch ( IOException e )
6225                 {
6226                     logError( "IOException: " + e.getMessage(), e );
6227                 }
6228             }
6229         }
6230 
6231         getLog().warn( "Unable to find the resource '" + inputResourceName + "'. Using default Javadoc resources." );
6232 
6233         return Optional.empty();
6234     }
6235 
6236     /**
6237      * @param classPath a not null String list of files where resource will be looked up
6238      * @param resource a not null resource to find in the class path
6239      * @return the resource from the given classpath or null if not found
6240      * @see ClassLoader#getResource(String)
6241      * @since 2.6
6242      */
6243     private URL getResource( final List<String> classPath, final String resource )
6244     {
6245         List<URL> urls = new ArrayList<>( classPath.size() );
6246         for ( String filename : classPath )
6247         {
6248             try
6249             {
6250                 urls.add( new File( filename ).toURI().toURL() );
6251             }
6252             catch ( MalformedURLException e )
6253             {
6254                 getLog().error( "MalformedURLException: " + e.getMessage() );
6255             }
6256         }
6257 
6258         URLClassLoader javadocClassLoader = new URLClassLoader( urls.toArray( new URL[urls.size()] ), null );
6259         try
6260         {
6261             return javadocClassLoader.getResource( resource );
6262         }
6263         finally
6264         {
6265             try
6266             {
6267                 javadocClassLoader.close();
6268             }
6269             catch ( IOException ex )
6270             {
6271                 // ignore
6272             }
6273         }
6274     }
6275 
6276     /**
6277      * Get the full javadoc goal. Loads the plugin's pom.properties to get the current plugin version.
6278      *
6279      * @return <code>org.apache.maven.plugins:maven-javadoc-plugin:CURRENT_VERSION:[test-]javadoc</code>
6280      */
6281     private String getFullJavadocGoal()
6282     {
6283         String javadocPluginVersion = null;
6284         String resource = "META-INF/maven/org.apache.maven.plugins/maven-javadoc-plugin/pom.properties";
6285         try ( InputStream resourceAsStream 
6286                         = AbstractJavadocMojo.class.getClassLoader().getResourceAsStream( resource ) )
6287         {
6288             if ( resourceAsStream != null )
6289             {
6290                 Properties properties = new Properties();
6291                 properties.load( resourceAsStream );
6292                 if ( StringUtils.isNotEmpty( properties.getProperty( "version" ) ) )
6293                 {
6294                     javadocPluginVersion = properties.getProperty( "version" );
6295                 }
6296             }
6297         }
6298         catch ( IOException e )
6299         {
6300             // nop
6301         }
6302 
6303         StringBuilder sb = new StringBuilder();
6304 
6305         sb.append( "org.apache.maven.plugins:maven-javadoc-plugin:" );
6306         if ( StringUtils.isNotEmpty( javadocPluginVersion ) )
6307         {
6308             sb.append( javadocPluginVersion ).append( ":" );
6309         }
6310 
6311         if ( this instanceof TestJavadocReport )
6312         {
6313             sb.append( "test-javadoc" );
6314         }
6315         else
6316         {
6317             sb.append( "javadoc" );
6318         }
6319 
6320         return sb.toString();
6321     }
6322 
6323     /**
6324      * Using Maven, a Javadoc link is given by <code>${project.url}/apidocs</code>.
6325      *
6326      * @return the detected Javadoc links using the Maven conventions for all modules defined in the current project
6327      *         or an empty list
6328      * @throws MavenReportException if any
6329      * @see #detectOfflineLinks
6330      * @see #reactorProjects
6331      * @since 2.6
6332      */
6333     private List<OfflineLink> getModulesLinks()
6334         throws MavenReportException
6335     {
6336         List<MavenProject> aggregatedProjects = reactorProjects;
6337         if ( !detectOfflineLinks || isAggregator() || aggregatedProjects.isEmpty() )
6338         {
6339             return Collections.emptyList();
6340         }
6341 
6342         getLog().debug( "Trying to add links for modules..." );
6343 
6344         Set<String> dependencyArtifactIds = new HashSet<>();
6345         final Set<Artifact> dependencyArtifacts = project.getDependencyArtifacts();
6346         for ( Artifact artifact : dependencyArtifacts )
6347         {
6348             dependencyArtifactIds.add( artifact.getId() );
6349         }
6350 
6351         List<OfflineLink> modulesLinks = new ArrayList<>();
6352         String javadocDirRelative = PathUtils.toRelative( project.getBasedir(), getOutputDirectory() );
6353         for ( MavenProject p : aggregatedProjects )
6354         {
6355             if ( !dependencyArtifactIds.contains( p.getArtifact().getId() ) || ( p.getUrl() == null ) )
6356             {
6357                 continue;
6358             }
6359 
6360             File location = new File( p.getBasedir(), javadocDirRelative );
6361 
6362             if ( !location.exists() )
6363             {
6364                 if ( getLog().isDebugEnabled() )
6365                 {
6366                     getLog().debug( "Javadoc directory not found: " + location );
6367                 }
6368 
6369                 String javadocGoal = getFullJavadocGoal();
6370                 getLog().info(
6371                     "The goal '" + javadocGoal + "' has not been previously called for the module: '" + p.getId()
6372                         + "'. Trying to invoke it..." );
6373 
6374                 File invokerDir = new File( project.getBuild().getDirectory(), "invoker" );
6375                 invokerDir.mkdirs();
6376                 File invokerLogFile = FileUtils.createTempFile( "maven-javadoc-plugin", ".txt", invokerDir );
6377                 try
6378                 {
6379                     JavadocUtil.invokeMaven( getLog(), new File( localRepository.getBasedir() ), p.getFile(),
6380                                              Collections.singletonList( javadocGoal ), null, invokerLogFile,
6381                                              session.getRequest().getGlobalSettingsFile() );
6382                 }
6383                 catch ( MavenInvocationException e )
6384                 {
6385                     logError( "MavenInvocationException: " + e.getMessage(), e );
6386 
6387                     String invokerLogContent = JavadocUtil.readFile( invokerLogFile, null /* platform encoding */ );
6388 
6389                     // TODO: Why are we only interested in cases where the JVM won't start?
6390                     // [MJAVADOC-275][jdcasey] I changed the logic here to only throw an error WHEN
6391                     //   the JVM won't start (opposite of what it was).
6392                     if ( invokerLogContent != null && invokerLogContent.contains( JavadocUtil.ERROR_INIT_VM ) )
6393                     {
6394                         throw new MavenReportException( e.getMessage(), e );
6395                     }
6396                 }
6397                 finally
6398                 {
6399                     // just create the directory to prevent repeated invocations..
6400                     if ( !location.exists() )
6401                     {
6402                         getLog().warn( "Creating fake javadoc directory to prevent repeated invocations: " + location );
6403                         location.mkdirs();
6404                     }
6405                 }
6406             }
6407 
6408             if ( location.exists() )
6409             {
6410                 String url = getJavadocLink( p );
6411 
6412                 OfflineLink ol = new OfflineLink();
6413                 ol.setUrl( url );
6414                 ol.setLocation( location.getAbsolutePath() );
6415 
6416                 if ( getLog().isDebugEnabled() )
6417                 {
6418                     getLog().debug( "Added Javadoc offline link: " + url + " for the module: " + p.getId() );
6419                 }
6420 
6421                 modulesLinks.add( ol );
6422             }
6423         }
6424 
6425         return modulesLinks;
6426     }
6427 
6428     /**
6429      * Using Maven, a Javadoc link is given by <code>${project.url}/apidocs</code>.
6430      *
6431      * @return the detected Javadoc links using the Maven conventions for all dependencies defined in the current
6432      *         project or an empty list.
6433      * @see #detectLinks
6434      * @see #isValidJavadocLink
6435      * @since 2.6
6436      */
6437     private List<String> getDependenciesLinks()
6438     {
6439         if ( !detectLinks )
6440         {
6441             return Collections.emptyList();
6442         }
6443 
6444         getLog().debug( "Trying to add links for dependencies..." );
6445 
6446         List<String> dependenciesLinks = new ArrayList<>();
6447 
6448         final Set<Artifact> dependencies = project.getDependencyArtifacts();
6449         for ( Artifact artifact : dependencies )
6450         {
6451             if ( artifact.getFile() == null || !artifact.getFile().exists() )
6452             {
6453                 continue;
6454             }
6455 
6456             Optional<DependencyLink> depLink =
6457                 this.dependencyLinks.stream().filter( d -> matches( d, artifact ) ).findAny();
6458             
6459             final String url;
6460             final boolean detected;
6461             if ( depLink.isPresent() )
6462             {
6463                 url = depLink.get().getUrl();
6464                 detected = false;
6465             }
6466             else
6467             {
6468                 try
6469                 {
6470                     MavenProject artifactProject =
6471                         mavenProjectBuilder.build( artifact, getProjectBuildingRequest( project ) ).getProject();
6472 
6473                     url = getJavadocLink( artifactProject );
6474                     detected = true;
6475                 }
6476                 catch ( ProjectBuildingException e )
6477                 {
6478                     logError( "ProjectBuildingException for " + artifact.toString() + ": " + e.getMessage(), e );
6479                     continue;
6480                 }
6481             }
6482             
6483             if ( isValidJavadocLink( url, detected ) )
6484             {
6485                 getLog().debug( "Added Javadoc link: " + url + " for " + artifact.getId() );
6486 
6487                 dependenciesLinks.add( url );
6488             }
6489         }
6490 
6491         return dependenciesLinks;
6492     }
6493 
6494     private boolean matches( DependencyLink d, Artifact artifact )
6495     {
6496         if ( d.getGroupId() != null && !d.getGroupId().equals( artifact.getGroupId() ) )
6497         {
6498             return false;
6499         }
6500         if ( d.getArtifactId() != null && !d.getArtifactId().equals( artifact.getArtifactId() ) )
6501         {
6502             return false;
6503         }
6504         if ( d.getClassifier() != null && !d.getClassifier().equals( artifact.getClassifier() ) )
6505         {
6506             return false;
6507         }
6508         return true;
6509     }
6510 
6511     /**
6512      * @return if {@link #detectJavaApiLink}, the Java API link based on the {@link #javaApiLinks} properties and the
6513      *         value of the <code>source</code> parameter in the
6514      *         <code>org.apache.maven.plugins:maven-compiler-plugin</code>
6515      *         defined in <code>${project.build.plugins}</code> or in <code>${project.build.pluginManagement}</code>,
6516      *         or the {@link #javadocRuntimeVersion}, or <code>null</code> if not defined.
6517      * @see #detectJavaApiLink
6518      * @see #javaApiLinks
6519      * @see <a href="http://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#source">source parameter</a>
6520      * @since 2.6
6521      */
6522     protected final OfflineLink getDefaultJavadocApiLink()
6523     {
6524         if ( !detectJavaApiLink )
6525         {
6526             return null;
6527         }
6528 
6529         final JavaVersion javaApiversion;
6530         if ( release != null )
6531         {
6532             javaApiversion = JavaVersion.parse( release );
6533         }
6534         else if ( source != null && !source.isEmpty() )
6535         {
6536             javaApiversion = JavaVersion.parse( source );
6537         }
6538         else
6539         {
6540             final String pluginId = "org.apache.maven.plugins:maven-compiler-plugin";
6541             String sourceConfigured = getPluginParameter( project, pluginId, "source" );
6542             if ( sourceConfigured != null )
6543             {
6544                 javaApiversion = JavaVersion.parse( sourceConfigured );
6545             }
6546             else
6547             {
6548                 getLog().debug( "No maven-compiler-plugin defined in ${build.plugins} or in "
6549                                     + "${project.build.pluginManagement} for the " + project.getId()
6550                                     + ". Added Javadoc API link according the javadoc executable version i.e.: "
6551                                     + javadocRuntimeVersion );
6552 
6553                 javaApiversion = javadocRuntimeVersion;
6554             }
6555         }
6556 
6557         final String javaApiKey;
6558         if ( javaApiversion.asMajor().isAtLeast( "9" ) )
6559         {
6560             javaApiKey = "api_" + javaApiversion.asMajor();
6561         }
6562         else
6563         {
6564             javaApiKey = "api_1." + javaApiversion.asMajor().toString().charAt( 0 );
6565         }
6566 
6567         final String javaApiLink;
6568         if ( javaApiLinks != null && javaApiLinks.containsKey( javaApiKey ) )
6569         {
6570             javaApiLink = javaApiLinks.getProperty( javaApiKey );
6571         }
6572         else if ( javaApiversion.isAtLeast( "11" ) )
6573         {
6574             javaApiLink =
6575                 String.format( "https://docs.oracle.com/en/java/javase/%s/docs/api/", javaApiversion.getValue( 1 ) );
6576         }
6577         else if ( javaApiversion.asMajor().isAtLeast( "6" ) )
6578         {
6579             javaApiLink =
6580                 String.format( "https://docs.oracle.com/javase/%s/docs/api/", javaApiversion.asMajor().getValue( 1 ) );
6581         }
6582         else if ( javaApiversion.isAtLeast( "1.5" ) )
6583         {
6584             javaApiLink = "https://docs.oracle.com/javase/1.5.0/docs/api/";
6585         }
6586         else
6587         {
6588             javaApiLink = null;
6589         }
6590 
6591         if ( getLog().isDebugEnabled() )
6592         {
6593             if ( javaApiLink != null )
6594             {
6595                 getLog().debug( "Found Java API link: " + javaApiLink );
6596             }
6597             else
6598             {
6599                 getLog().debug( "No Java API link found." );
6600             }
6601         }
6602 
6603         if ( javaApiLink == null )
6604         {
6605             return null;
6606         }
6607 
6608         final Path javaApiListFile;
6609         final String resourceName;
6610         if ( javaApiversion.isAtLeast( "10" ) )
6611         {
6612             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve( "element-list" );
6613             resourceName = "java-api-element-list-" + javaApiversion.toString().substring( 0, 2 );
6614         }
6615         else if ( javaApiversion.asMajor().isAtLeast( "9" ) )
6616         {
6617             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve( "package-list" );
6618             resourceName = "java-api-package-list-9";
6619         }
6620         else
6621         {
6622             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve( "package-list" );
6623             resourceName = "java-api-package-list-1." + javaApiversion.asMajor().toString().charAt( 0 );
6624         }
6625 
6626         OfflineLink link = new OfflineLink();
6627         link.setLocation( javaApiListFile.getParent().toAbsolutePath().toString() );
6628         link.setUrl( javaApiLink );
6629 
6630         InputStream in = this.getClass().getResourceAsStream( resourceName );
6631         if ( in != null )
6632         {
6633             try ( InputStream closableIS = in )
6634             {
6635                 // TODO only copy when changed
6636                 Files.copy( closableIS, javaApiListFile, StandardCopyOption.REPLACE_EXISTING );
6637             }
6638             catch ( IOException ioe )
6639             {
6640                 logError( "Can't get " + resourceName + ": " + ioe.getMessage(), ioe );
6641                 return null;
6642             }
6643         }
6644 
6645         return link;
6646     }
6647 
6648     /**
6649      * Follows all of the given links, and returns their last redirect locations. Ordering is kept.
6650      * This is necessary because javadoc tool doesn't follow links, see JDK-8190312 (MJAVADOC-427, MJAVADOC-487)
6651      *
6652      * @param links Links to follow.
6653      * @return Last redirect location of all the links.
6654      */
6655     private Set<String> followLinks( Set<String> links )
6656     {
6657         Set<String> redirectLinks = new LinkedHashSet<>( links.size() );
6658         for ( String link : links )
6659         {
6660             try
6661             {
6662                 redirectLinks.add( JavadocUtil.getRedirectUrl( new URI( link ).toURL(), settings ).toString() );
6663             }
6664             catch ( Exception e )
6665             {
6666                 // only print in debug, it should have been logged already in warn/error because link isn't valid
6667                 getLog().debug( "Could not follow " + link + ". Reason: " + e.getMessage() );
6668 
6669                 // Even when link produces error it should be kept in the set because the error might be caused by
6670                 // incomplete redirect configuration on the server side.
6671                 // This partially restores the previous behaviour before fix for MJAVADOC-427
6672                 redirectLinks.add( link );
6673             }
6674         }
6675         return redirectLinks;
6676     }
6677 
6678     /**
6679      * @param link not null
6680      * @param detecting <code>true</code> if the link is generated by
6681      * <code>detectLinks</code>, or <code>false</code> otherwise
6682      * @return <code>true</code> if the link has a <code>/package-list</code>, <code>false</code> otherwise.
6683      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#package-list">
6684      *      package-list spec</a>
6685      * @since 2.6
6686      */
6687     protected boolean isValidJavadocLink( String link, boolean detecting )
6688     {
6689         try
6690         {
6691             final URI packageListUri;
6692             final URI elementListUri;
6693 
6694             if ( link.trim().toLowerCase( Locale.ENGLISH ).startsWith( "http:" ) || link.trim().toLowerCase(
6695                 Locale.ENGLISH ).startsWith( "https:" ) || link.trim().toLowerCase( Locale.ENGLISH ).startsWith(
6696                 "ftp:" ) || link.trim().toLowerCase( Locale.ENGLISH ).startsWith( "file:" ) )
6697             {
6698                 packageListUri = new URI( link + '/' + PACKAGE_LIST );
6699                 elementListUri = new URI( link + '/' + ELEMENT_LIST );
6700             }
6701             else
6702             {
6703                 // links can be relative paths or files
6704                 File dir = new File( link );
6705                 if ( !dir.isAbsolute() )
6706                 {
6707                     dir = new File( getOutputDirectory(), link );
6708                 }
6709                 if ( !dir.isDirectory() )
6710                 {
6711                     if ( detecting )
6712                     {
6713                         getLog().warn( "The given File link: " + dir + " is not a dir." );
6714                     }
6715                     else
6716                     {
6717                         getLog().error( "The given File link: " + dir + " is not a dir." );
6718                     }
6719                 }
6720                 packageListUri = new File( dir, PACKAGE_LIST ).toURI();
6721                 elementListUri = new File( dir, ELEMENT_LIST ).toURI();
6722             }
6723 
6724 
6725             try
6726             {
6727                 if ( JavadocUtil.isValidElementList( elementListUri.toURL(), settings, validateLinks ) )
6728                 {
6729                     return true;
6730                 }
6731             }
6732             catch ( IOException e )
6733             {
6734             }
6735 
6736             if ( JavadocUtil.isValidPackageList( packageListUri.toURL(), settings, validateLinks ) )
6737             {
6738                 return true;
6739             }
6740 
6741             if ( getLog().isErrorEnabled() )
6742             {
6743                 if ( detecting )
6744                 {
6745                     getLog().warn( "Invalid links: "
6746                                     + link + " with /" + PACKAGE_LIST + " or / " + ELEMENT_LIST + ". Ignored it." );
6747                 }
6748                 else
6749                 {
6750                     getLog().error( "Invalid links: "
6751                                     + link + " with /" + PACKAGE_LIST + " or / " + ELEMENT_LIST + ". Ignored it." );
6752                 }
6753             }
6754 
6755             return false;
6756         }
6757         catch ( URISyntaxException e )
6758         {
6759             if ( getLog().isErrorEnabled() )
6760             {
6761                 if ( detecting )
6762                 {
6763                     getLog().warn( "Malformed link: " + e.getInput() + ". Ignored it." );
6764                 }
6765                 else
6766                 {
6767                     getLog().error( "Malformed link: " + e.getInput() + ". Ignored it." );
6768                 }
6769             }
6770             return false;
6771         }
6772         catch ( IOException e )
6773         {
6774             if ( getLog().isErrorEnabled() )
6775             {
6776                 if ( detecting )
6777                 {
6778                     getLog().warn( "Error fetching link: " + link + ". Ignored it." );
6779                 }
6780                 else
6781                 {
6782                     getLog().error( "Error fetching link: " + link + ". Ignored it." );
6783                 }
6784             }
6785             return false;
6786         }
6787     }
6788 
6789     /**
6790      * Write a debug javadoc script in case of command line error or in debug mode.
6791      *
6792      * @param cmdLine                the current command line as string, not null.
6793      * @param javadocOutputDirectory the output dir, not null.
6794      * @see #executeJavadocCommandLine(Commandline, File)
6795      * @since 2.6
6796      */
6797     private void writeDebugJavadocScript( String cmdLine, File javadocOutputDirectory )
6798     {
6799         File commandLineFile = new File( javadocOutputDirectory, DEBUG_JAVADOC_SCRIPT_NAME );
6800         commandLineFile.getParentFile().mkdirs();
6801 
6802         try
6803         {
6804             FileUtils.fileWrite( commandLineFile.getAbsolutePath(), null /* platform encoding */, cmdLine );
6805 
6806             if ( !SystemUtils.IS_OS_WINDOWS )
6807             {
6808                 Runtime.getRuntime().exec( new String[]{ "chmod", "a+x", commandLineFile.getAbsolutePath() } );
6809             }
6810         }
6811         catch ( IOException e )
6812         {
6813             logError( "Unable to write '" + commandLineFile.getName() + "' debug script file", e );
6814         }
6815     }
6816 
6817     /**
6818      * Check if the Javadoc JVM is correctly started or not.
6819      *
6820      * @param output the command line output, not null.
6821      * @return <code>true</code> if Javadoc output command line contains Javadoc word, <code>false</code> otherwise.
6822      * @see #executeJavadocCommandLine(Commandline, File)
6823      * @since 2.6.1
6824      */
6825     private boolean isJavadocVMInitError( String output )
6826     {
6827         /*
6828          * see main.usage and main.Building_tree keys from
6829          * com.sun.tools.javadoc.resources.javadoc bundle in tools.jar
6830          */
6831         return !( output.contains( "Javadoc" ) || output.contains( "javadoc" ) );
6832     }
6833 
6834     // ----------------------------------------------------------------------
6835     // Static methods
6836     // ----------------------------------------------------------------------
6837 
6838     /**
6839      * @param p not null
6840      * @return the javadoc link based on the project url i.e. <code>${project.url}/${destDir}</code> where
6841      *         <code>destDir</code> is configued in the Javadoc plugin configuration (<code>apidocs</code> by default).
6842      * @since 2.6
6843      */
6844     private static String getJavadocLink( MavenProject p )
6845     {
6846         if ( p.getUrl() == null )
6847         {
6848             return null;
6849         }
6850 
6851         String url = cleanUrl( p.getUrl() );
6852         String destDir = "apidocs"; // see JavadocReport#destDir
6853 
6854         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
6855         String destDirConfigured = getPluginParameter( p, pluginId, "destDir" );
6856         if ( destDirConfigured != null )
6857         {
6858             destDir = destDirConfigured;
6859         }
6860 
6861         return url + "/" + destDir;
6862     }
6863 
6864     /**
6865      * @param url could be null.
6866      * @return the url cleaned or empty if url was null.
6867      * @since 2.6
6868      */
6869     private static String cleanUrl( String url )
6870     {
6871         if ( url == null )
6872         {
6873             return "";
6874         }
6875 
6876         url = url.trim();
6877         while ( url.endsWith( "/" ) )
6878         {
6879             url = url.substring( 0, url.lastIndexOf( "/" ) );
6880         }
6881 
6882         return url;
6883     }
6884 
6885     /**
6886      * @param p        not null
6887      * @param pluginId not null key of the plugin defined in {@link org.apache.maven.model.Build#getPluginsAsMap()}
6888      *                 or in {@link org.apache.maven.model.PluginManagement#getPluginsAsMap()}
6889      * @return the Maven plugin defined in <code>${project.build.plugins}</code> or in
6890      *         <code>${project.build.pluginManagement}</code>, or <code>null</code> if not defined.
6891      * @since 2.6
6892      */
6893     private static Plugin getPlugin( MavenProject p, String pluginId )
6894     {
6895         if ( ( p.getBuild() == null ) || ( p.getBuild().getPluginsAsMap() == null ) )
6896         {
6897             return null;
6898         }
6899 
6900         Plugin plugin = p.getBuild().getPluginsAsMap().get( pluginId );
6901 
6902         if ( ( plugin == null ) && ( p.getBuild().getPluginManagement() != null ) && (
6903             p.getBuild().getPluginManagement().getPluginsAsMap() != null ) )
6904         {
6905             plugin = p.getBuild().getPluginManagement().getPluginsAsMap().get( pluginId );
6906         }
6907 
6908         return plugin;
6909     }
6910 
6911     /**
6912      * @param p        not null
6913      * @param pluginId not null
6914      * @param param    not null
6915      * @return the simple parameter as String defined in the plugin configuration by <code>param</code> key
6916      *         or <code>null</code> if not found.
6917      * @since 2.6
6918      */
6919     private static String getPluginParameter( MavenProject p, String pluginId, String param )
6920     {
6921 //        p.getGoalConfiguration( pluginGroupId, pluginArtifactId, executionId, goalId );
6922         Plugin plugin = getPlugin( p, pluginId );
6923         if ( plugin != null )
6924         {
6925             Xpp3Dom xpp3Dom = (Xpp3Dom) plugin.getConfiguration();
6926             if ( xpp3Dom != null && xpp3Dom.getChild( param ) != null
6927                 && StringUtils.isNotEmpty( xpp3Dom.getChild( param ).getValue() ) )
6928             {
6929                 return xpp3Dom.getChild( param ).getValue();
6930             }
6931         }
6932 
6933         return null;
6934     }
6935 
6936     /**
6937      * Construct the output file for the generated javadoc-options XML file, after creating the
6938      * javadocOptionsDir if necessary. This method does NOT write to the file in question.
6939      *
6940      * @return The options {@link File} file.
6941      * @since 2.7
6942      */
6943     protected final File getJavadocOptionsFile()
6944     {
6945         if ( javadocOptionsDir != null && !javadocOptionsDir.exists() )
6946         {
6947             javadocOptionsDir.mkdirs();
6948         }
6949 
6950         return new File( javadocOptionsDir, "javadoc-options-" + getAttachmentClassifier() + ".xml" );
6951     }
6952 
6953     /**
6954      * Generate a javadoc-options XML file, for either bundling with a javadoc-resources artifact OR
6955      * supplying to a distro module in a includeDependencySources configuration, so the javadoc options
6956      * from this execution can be reconstructed and merged in the distro build.
6957      *
6958      * @return {@link JavadocOptions}
6959      * @throws IOException {@link IOException}
6960      * @since 2.7
6961      */
6962     protected final JavadocOptions buildJavadocOptions()
6963         throws IOException
6964     {
6965         JavadocOptions options = new JavadocOptions();
6966 
6967         options.setBootclasspathArtifacts( toList( bootclasspathArtifacts ) );
6968         options.setDocfilesSubdirsUsed( docfilessubdirs );
6969         options.setDocletArtifacts( toList( docletArtifact, docletArtifacts ) );
6970         options.setExcludedDocfilesSubdirs( excludedocfilessubdir );
6971         options.setExcludePackageNames( toList( excludePackageNames ) );
6972         options.setGroups( toList( groups ) );
6973         options.setLinks( links );
6974         options.setOfflineLinks( toList( offlineLinks ) );
6975         options.setResourcesArtifacts( toList( resourcesArtifacts ) );
6976         options.setTagletArtifacts( toList( tagletArtifact, tagletArtifacts ) );
6977         options.setTaglets( toList( taglets ) );
6978         options.setTags( toList( tags ) );
6979 
6980         if ( getProject() != null && getJavadocDirectory() != null )
6981         {
6982             options.setJavadocResourcesDirectory(
6983                 toRelative( getProject().getBasedir(), getJavadocDirectory().getAbsolutePath() ) );
6984         }
6985 
6986         File optionsFile = getJavadocOptionsFile();
6987 
6988         try ( Writer writer = WriterFactory.newXmlWriter( optionsFile ) )
6989         {
6990             new JavadocOptionsXpp3Writer().write( writer, options );
6991         }
6992 
6993         return options;
6994     }
6995 
6996     /**
6997      * Override this if you need to provide a bundle attachment classifier, as in the case of test
6998      * javadocs.
6999      * @return The attachment classifier.
7000      */
7001     protected String getAttachmentClassifier()
7002     {
7003         return JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER;
7004     }
7005 
7006     /**
7007      * Logs an error with throwable content only if in debug.
7008      *
7009      * @param message The message which should be announced.
7010      * @param t The throwable part of the message.
7011      */
7012     protected void logError( String message, Throwable t )
7013     {
7014         if ( getLog().isDebugEnabled() )
7015         {
7016             getLog().error( message, t );
7017         }
7018         else
7019         {
7020             getLog().error( message );
7021         }
7022     }
7023 
7024     /**
7025      * @param prefix The prefix of the exception.
7026      * @param e The exception.
7027      * @throws MojoExecutionException {@link MojoExecutionException} issue while generating report
7028      */
7029     protected void failOnError( String prefix, Exception e )
7030         throws MojoExecutionException
7031     {
7032         if ( failOnError )
7033         {
7034             if ( e instanceof RuntimeException )
7035             {
7036                 throw (RuntimeException) e;
7037             }
7038             throw new MojoExecutionException( prefix + ": " + e.getMessage(), e );
7039         }
7040 
7041         getLog().error( prefix + ": " + e.getMessage(), e );
7042     }
7043 
7044 
7045     /**
7046      *
7047      * @return List of projects to be part of aggregated javadoc
7048      */
7049     private List<MavenProject> getAggregatedProjects()
7050     {
7051         if ( this.reactorProjects == null )
7052         {
7053            return Collections.emptyList();
7054         }
7055         Map<Path, MavenProject> reactorProjectsMap = new HashMap<>();
7056         for ( MavenProject reactorProject : this.reactorProjects )
7057         {
7058             if ( !isSkippedJavadoc( reactorProject ) && //
7059                     !isSkippedModule( reactorProject ) )
7060             {
7061                 reactorProjectsMap.put( reactorProject.getBasedir().toPath(), reactorProject );
7062             }
7063         }
7064 
7065         return new ArrayList<>( modulesForAggregatedProject( project, reactorProjectsMap ) );
7066     }
7067 
7068     /**
7069      *
7070      * @return <code>true</code> if the module need to be skipped from aggregate generation
7071      */
7072     protected boolean isSkippedModule( MavenProject mavenProject )
7073     {
7074         if ( StringUtils.isEmpty( this.skippedModules ) )
7075         {
7076             return false;
7077         }
7078         List<String> modulesToSkip = Arrays.asList( StringUtils.split( this.skippedModules, ',' ) );
7079         return modulesToSkip.contains( mavenProject.getArtifactId() );
7080     }
7081 
7082     /**
7083      *
7084      * @return <code>true</code> if the pom configuration skip javadoc generation for the project
7085      */
7086     protected boolean isSkippedJavadoc( MavenProject mavenProject )
7087     {
7088         String property = mavenProject.getProperties().getProperty( "maven.javadoc.skip" );
7089         if ( property != null )
7090         {
7091             boolean skip = BooleanUtils.toBoolean( property );
7092             getLog().debug( "isSkippedJavadoc " + mavenProject + " " + skip );
7093             return skip;
7094         }
7095         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
7096         property = getPluginParameter( mavenProject, pluginId, "skip" );
7097         if ( property != null )
7098         {
7099             boolean skip = BooleanUtils.toBoolean( property );
7100             getLog().debug( "isSkippedJavadoc " + mavenProject + " " + skip );
7101             return skip;
7102         }
7103         if ( mavenProject.getParent() != null )
7104         {
7105             return isSkippedJavadoc( mavenProject.getParent() );
7106         }
7107         getLog().debug( "isSkippedJavadoc " + mavenProject + " " + false );
7108         return false;
7109     }
7110 
7111 }