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