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             Throwable cause = e;
5547             while ( cause.getCause() != null )
5548             {
5549                 cause = cause.getCause();
5550             }
5551             getLog().warn( e.getMessage() );
5552         }
5553         return resolvePathResult;
5554     }
5555 
5556     private Path findMainDescriptor( Collection<Path> roots ) throws MavenReportException
5557     {
5558         for ( Map.Entry<Path, Collection<String>> entry : getFiles( roots ).entrySet() )
5559         {
5560             if ( entry.getValue().contains( "module-info.java" ) )
5561             {
5562                 return entry.getKey().resolve( "module-info.java" );
5563             }
5564         }
5565         return null;
5566     }
5567 
5568     /**
5569      * Add Standard Doclet Options.
5570      * <br/>
5571      * The <a href="package-summary.html#Standard_Doclet_Options">package documentation</a> details the
5572      * Standard Doclet Options wrapped by this Plugin.
5573      *
5574      * @param javadocOutputDirectory not null
5575      * @param arguments              not null
5576      * @throws MavenReportException if any
5577      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#standard">
5578      *      https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#standard</a>
5579      */
5580     private void addStandardDocletOptions( File javadocOutputDirectory,
5581                                            List<String> arguments,
5582                                            Set<OfflineLink> offlineLinks )
5583         throws MavenReportException
5584     {
5585         validateStandardDocletOptions();
5586 
5587         // all options in alphabetical order
5588 
5589         addArgIf( arguments, author, "-author" );
5590 
5591         addArgIfNotEmpty( arguments, "-bottom", JavadocUtil.quotedArgument( getBottomText() ), false, false );
5592 
5593         if ( !isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) )
5594         {
5595             addArgIf( arguments, breakiterator, "-breakiterator", SINCE_JAVADOC_1_4 );
5596         }
5597 
5598         addArgIfNotEmpty( arguments, "-charset", JavadocUtil.quotedArgument( getCharset() ) );
5599 
5600         addArgIfNotEmpty( arguments, "-d", JavadocUtil.quotedPathArgument( javadocOutputDirectory.toString() ) );
5601 
5602         addArgIfNotEmpty( arguments, "-docencoding", JavadocUtil.quotedArgument( getDocencoding() ) );
5603 
5604         addArgIf( arguments, docfilessubdirs, "-docfilessubdirs", SINCE_JAVADOC_1_4 );
5605 
5606         addArgIf( arguments, StringUtils.isNotEmpty( doclint ), "-Xdoclint:" + getDoclint(), SINCE_JAVADOC_1_8 );
5607 
5608         addArgIfNotEmpty( arguments, "-doctitle", JavadocUtil.quotedArgument( getDoctitle() ), false, false );
5609 
5610         if ( docfilessubdirs )
5611         {
5612             addArgIfNotEmpty( arguments, "-excludedocfilessubdir",
5613                               JavadocUtil.quotedPathArgument( excludedocfilessubdir ), SINCE_JAVADOC_1_4 );
5614         }
5615 
5616         addArgIfNotEmpty( arguments, "-footer", JavadocUtil.quotedArgument( footer ), false, false );
5617 
5618         addGroups( arguments );
5619 
5620         addArgIfNotEmpty( arguments, "-header", JavadocUtil.quotedArgument( header ), false, false );
5621 
5622         Optional<File> helpFile = getHelpFile( javadocOutputDirectory );
5623         if ( helpFile.isPresent() )
5624         {
5625             addArgIfNotEmpty( arguments, "-helpfile",
5626                               JavadocUtil.quotedPathArgument( helpFile.get().getAbsolutePath() ) );
5627         }
5628 
5629         addArgIf( arguments, keywords, "-keywords", SINCE_JAVADOC_1_4_2 );
5630 
5631         addLinkArguments( arguments );
5632 
5633         addLinkofflineArguments( arguments, offlineLinks );
5634 
5635         addArgIf( arguments, linksource, "-linksource", SINCE_JAVADOC_1_4 );
5636 
5637         if ( sourcetab > 0 )
5638         {
5639             if ( javadocRuntimeVersion == SINCE_JAVADOC_1_4_2 )
5640             {
5641                 addArgIfNotEmpty( arguments, "-linksourcetab", String.valueOf( sourcetab ) );
5642             }
5643             addArgIfNotEmpty( arguments, "-sourcetab", String.valueOf( sourcetab ), SINCE_JAVADOC_1_5 );
5644         }
5645 
5646         addArgIf( arguments, nocomment, "-nocomment", SINCE_JAVADOC_1_4 );
5647 
5648         addArgIf( arguments, nodeprecated, "-nodeprecated" );
5649 
5650         addArgIf( arguments, nodeprecatedlist, "-nodeprecatedlist" );
5651 
5652         addArgIf( arguments, nohelp, "-nohelp" );
5653 
5654         addArgIf( arguments, noindex, "-noindex" );
5655 
5656         addArgIf( arguments, nonavbar, "-nonavbar" );
5657 
5658         addArgIf( arguments, nooverview, "-nooverview" );
5659 
5660         addArgIfNotEmpty( arguments, "-noqualifier", JavadocUtil.quotedArgument( noqualifier ), SINCE_JAVADOC_1_4 );
5661 
5662         addArgIf( arguments, nosince, "-nosince" );
5663 
5664         addArgIf( arguments, notimestamp, "-notimestamp", SINCE_JAVADOC_1_5 );
5665 
5666         addArgIf( arguments, notree, "-notree" );
5667 
5668         addArgIfNotEmpty( arguments, "-packagesheader", JavadocUtil.quotedArgument( packagesheader ),
5669                           SINCE_JAVADOC_1_4_2 );
5670 
5671         if ( !isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) ) // Sun bug: 4714350
5672         {
5673             addArgIf( arguments, quiet, "-quiet", SINCE_JAVADOC_1_4 );
5674         }
5675 
5676         addArgIf( arguments, serialwarn, "-serialwarn" );
5677 
5678         addArgIf( arguments, splitindex, "-splitindex" );
5679 
5680         Optional<File> stylesheetfile = getStylesheetFile( javadocOutputDirectory );
5681         
5682         if ( stylesheetfile.isPresent() )
5683         {
5684             addArgIfNotEmpty( arguments, "-stylesheetfile",
5685                               JavadocUtil.quotedPathArgument( stylesheetfile.get().getAbsolutePath() ) );
5686         }
5687         
5688         addAddStyleSheets( arguments ); 
5689 
5690         if ( StringUtils.isNotEmpty( sourcepath ) && !isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) )
5691         {
5692             addArgIfNotEmpty( arguments, "-subpackages", subpackages, SINCE_JAVADOC_1_4 );
5693         }
5694 
5695         addArgIfNotEmpty( arguments, "-taglet", JavadocUtil.quotedArgument( taglet ), SINCE_JAVADOC_1_4 );
5696         addTaglets( arguments );
5697         addTagletsFromTagletArtifacts( arguments );
5698         addArgIfNotEmpty( arguments, "-tagletpath", JavadocUtil.quotedPathArgument( getTagletPath() ),
5699                           SINCE_JAVADOC_1_4 );
5700 
5701         addTags( arguments );
5702 
5703         addArgIfNotEmpty( arguments, "-top", JavadocUtil.quotedArgument( top ), false, false, SINCE_JAVADOC_1_6 );
5704 
5705         addArgIf( arguments, use, "-use" );
5706 
5707         addArgIf( arguments, version, "-version" );
5708 
5709         addArgIfNotEmpty( arguments, "-windowtitle", JavadocUtil.quotedArgument( getWindowtitle() ), false, false );
5710     }
5711 
5712     /**
5713      * Add <code>groups</code> parameter to arguments.
5714      *
5715      * @param arguments not null
5716      * @throws MavenReportException
5717      */
5718     private void addGroups( List<String> arguments )
5719         throws MavenReportException
5720     {
5721         Set<Group> groups = collectGroups();
5722         if ( isEmpty( groups ) )
5723         {
5724             return;
5725         }
5726 
5727         for ( Group group : groups )
5728         {
5729             if ( group == null || StringUtils.isEmpty( group.getTitle() ) || StringUtils.isEmpty(
5730                 group.getPackages() ) )
5731             {
5732                 if ( getLog().isWarnEnabled() )
5733                 {
5734                     getLog().warn( "A group option is empty. Ignore this option." );
5735                 }
5736             }
5737             else
5738             {
5739                 String groupTitle = StringUtils.replace( group.getTitle(), ",", "&#44;" );
5740                 addArgIfNotEmpty( arguments, "-group",
5741                                   JavadocUtil.quotedArgument( groupTitle ) + " " + JavadocUtil.quotedArgument(
5742                                       group.getPackages() ), true );
5743             }
5744         }
5745     }
5746 
5747     /**
5748      * Add <code>tags</code> parameter to arguments.
5749      *
5750      * @param arguments not null
5751      * @throws MavenReportException
5752      */
5753     private void addTags( List<String> arguments )
5754         throws MavenReportException
5755     {
5756         final String lineSeparator;
5757         if ( javadocRuntimeVersion.isBefore( "9" ) )
5758         {
5759             lineSeparator = " ";
5760         }
5761         else
5762         {
5763             lineSeparator = " \\\\" + SystemUtils.LINE_SEPARATOR;
5764         }
5765 
5766         for ( Tag tag : collectTags() )
5767         {
5768             if ( StringUtils.isEmpty( tag.getName() ) )
5769             {
5770                 if ( getLog().isWarnEnabled() )
5771                 {
5772                     getLog().warn( "A tag name is empty. Ignore this option." );
5773                 }
5774             }
5775             else
5776             {
5777                 String value = "\"" + tag.getName();
5778                 if ( StringUtils.isNotEmpty( tag.getPlacement() ) )
5779                 {
5780                     value += ":" + tag.getPlacement().replaceAll( "\\R", lineSeparator );
5781                     if ( StringUtils.isNotEmpty( tag.getHead() ) )
5782                     {
5783                         value += ":" + tag.getHead().replaceAll( "\\R", lineSeparator );
5784                     }
5785                 }
5786                 value += "\"";
5787                 addArgIfNotEmpty( arguments, "-tag", value, SINCE_JAVADOC_1_4 );
5788             }
5789         }
5790     }
5791 
5792     /**
5793      * Add <code>taglets</code> parameter to arguments.
5794      *
5795      * @param arguments not null
5796      */
5797     private void addTaglets( List<String> arguments )
5798     {
5799         if ( taglets == null )
5800         {
5801             return;
5802         }
5803 
5804         for ( Taglet taglet1 : taglets )
5805         {
5806             if ( ( taglet1 == null ) || ( StringUtils.isEmpty( taglet1.getTagletClass() ) ) )
5807             {
5808                 if ( getLog().isWarnEnabled() )
5809                 {
5810                     getLog().warn( "A taglet option is empty. Ignore this option." );
5811                 }
5812             }
5813             else
5814             {
5815                 addArgIfNotEmpty( arguments, "-taglet", JavadocUtil.quotedArgument( taglet1.getTagletClass() ),
5816                                   SINCE_JAVADOC_1_4 );
5817             }
5818         }
5819     }
5820 
5821     /**
5822      * Auto-detect taglets class name from <code>tagletArtifacts</code> and add them to arguments.
5823      *
5824      * @param arguments not null
5825      * @throws MavenReportException if any
5826      * @see JavadocUtil#getTagletClassNames(File)
5827      */
5828     private void addTagletsFromTagletArtifacts( List<String> arguments )
5829         throws MavenReportException
5830     {
5831         Set<TagletArtifact> tArtifacts = new LinkedHashSet<>();
5832         if ( tagletArtifacts != null && tagletArtifacts.length > 0 )
5833         {
5834             tArtifacts.addAll( Arrays.asList( tagletArtifacts ) );
5835         }
5836 
5837         if ( includeDependencySources )
5838         {
5839             try
5840             {
5841                 resolveDependencyBundles();
5842             }
5843             catch ( IOException e )
5844             {
5845                 throw new MavenReportException(
5846                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
5847             }
5848 
5849             if ( isNotEmpty( dependencyJavadocBundles ) )
5850             {
5851                 for ( JavadocBundle bundle : dependencyJavadocBundles )
5852                 {
5853                     JavadocOptions options = bundle.getOptions();
5854                     if ( options != null && isNotEmpty( options.getTagletArtifacts() ) )
5855                     {
5856                         tArtifacts.addAll( options.getTagletArtifacts() );
5857                     }
5858                 }
5859             }
5860         }
5861 
5862         if ( isEmpty( tArtifacts ) )
5863         {
5864             return;
5865         }
5866 
5867         List<String> tagletsPath = new ArrayList<>();
5868 
5869         for ( TagletArtifact aTagletArtifact : tArtifacts )
5870         {
5871             if ( ( StringUtils.isNotEmpty( aTagletArtifact.getGroupId() ) ) && ( StringUtils.isNotEmpty(
5872                 aTagletArtifact.getArtifactId() ) ) && ( StringUtils.isNotEmpty( aTagletArtifact.getVersion() ) ) )
5873             {
5874                 Artifact artifact;
5875                 try
5876                 {
5877                     artifact = createAndResolveArtifact( aTagletArtifact );
5878                 }
5879                 catch ( ArtifactResolverException e )
5880                 {
5881                     throw new MavenReportException( "Unable to resolve artifact:" + aTagletArtifact, e );
5882                 }
5883 
5884                 tagletsPath.add( artifact.getFile().getAbsolutePath() );
5885             }
5886         }
5887 
5888         tagletsPath = JavadocUtil.pruneFiles( tagletsPath );
5889 
5890         for ( String tagletJar : tagletsPath )
5891         {
5892             if ( !tagletJar.toLowerCase( Locale.ENGLISH ).endsWith( ".jar" ) )
5893             {
5894                 continue;
5895             }
5896 
5897             List<String> tagletClasses;
5898             try
5899             {
5900                 tagletClasses = JavadocUtil.getTagletClassNames( new File( tagletJar ) );
5901             }
5902             catch ( IOException e )
5903             {
5904                 if ( getLog().isWarnEnabled() )
5905                 {
5906                     getLog().warn( "Unable to auto-detect Taglet class names from '" + tagletJar
5907                                        + "'. Try to specify them with <taglets/>." );
5908                 }
5909                 if ( getLog().isDebugEnabled() )
5910                 {
5911                     getLog().debug( "IOException: " + e.getMessage(), e );
5912                 }
5913                 continue;
5914             }
5915             catch ( ClassNotFoundException e )
5916             {
5917                 if ( getLog().isWarnEnabled() )
5918                 {
5919                     getLog().warn( "Unable to auto-detect Taglet class names from '" + tagletJar
5920                                        + "'. Try to specify them with <taglets/>." );
5921                 }
5922                 if ( getLog().isDebugEnabled() )
5923                 {
5924                     getLog().debug( "ClassNotFoundException: " + e.getMessage(), e );
5925                 }
5926                 continue;
5927             }
5928             catch ( NoClassDefFoundError e )
5929             {
5930                 if ( getLog().isWarnEnabled() )
5931                 {
5932                     getLog().warn( "Unable to auto-detect Taglet class names from '" + tagletJar
5933                                        + "'. Try to specify them with <taglets/>." );
5934                 }
5935                 if ( getLog().isDebugEnabled() )
5936                 {
5937                     getLog().debug( "NoClassDefFoundError: " + e.getMessage(), e );
5938                 }
5939                 continue;
5940             }
5941 
5942             if ( tagletClasses != null && !tagletClasses.isEmpty() )
5943             {
5944                 for ( String tagletClass : tagletClasses )
5945                 {
5946                     addArgIfNotEmpty( arguments, "-taglet", JavadocUtil.quotedArgument( tagletClass ),
5947                                       SINCE_JAVADOC_1_4 );
5948                 }
5949             }
5950         }
5951     }
5952 
5953     /**
5954      * Execute the Javadoc command line
5955      *
5956      * @param cmd                    not null
5957      * @param javadocOutputDirectory not null
5958      * @throws MavenReportException if any errors occur
5959      */
5960     private void executeJavadocCommandLine( Commandline cmd, File javadocOutputDirectory )
5961             throws MavenReportException
5962     {
5963         if ( staleDataPath != null )
5964         {
5965             if ( !isUpToDate( cmd ) )
5966             {
5967                 doExecuteJavadocCommandLine( cmd, javadocOutputDirectory );
5968                 StaleHelper.writeStaleData( cmd, staleDataPath.toPath() );
5969             }
5970         }
5971         else
5972         {
5973             doExecuteJavadocCommandLine( cmd, javadocOutputDirectory );
5974         }
5975     }
5976 
5977     /**
5978      * Check if the javadoc is uptodate or not
5979      *
5980      * @param cmd                    not null
5981      * @return <code>true</code> is the javadoc is uptodate, <code>false</code> otherwise
5982      * @throws MavenReportException  if any error occur
5983      */
5984     private boolean isUpToDate( Commandline cmd )
5985             throws MavenReportException
5986     {
5987         try
5988         {
5989             String curdata = StaleHelper.getStaleData( cmd );
5990             Path cacheData = staleDataPath.toPath();
5991             String prvdata;
5992             if ( Files.isRegularFile( cacheData ) )
5993             {
5994                 prvdata = new String( Files.readAllBytes( cacheData ), StandardCharsets.UTF_8 );
5995             }
5996             else
5997             {
5998                 prvdata = null;
5999             }
6000             if ( curdata.equals( prvdata ) )
6001             {
6002                 getLog().info( "Skipping javadoc generation, everything is up to date." );
6003                 return true;
6004             }
6005             else
6006             {
6007                 if ( prvdata == null )
6008                 {
6009                     getLog().info( "No previous run data found, generating javadoc." );
6010                 }
6011                 else
6012                 {
6013                     getLog().info( "Configuration changed, re-generating javadoc." );
6014                 }
6015             }
6016         }
6017         catch ( IOException e )
6018         {
6019             throw new MavenReportException( "Error checking uptodate status", e );
6020         }
6021         return false;
6022     }
6023 
6024     /**
6025      * Execute the Javadoc command line
6026      *
6027      * @param cmd                    not null
6028      * @param javadocOutputDirectory not null
6029      * @throws MavenReportException if any errors occur
6030      */
6031     private void doExecuteJavadocCommandLine( Commandline cmd, File javadocOutputDirectory )
6032         throws MavenReportException
6033     {
6034         if ( getLog().isDebugEnabled() )
6035         {
6036             // no quoted arguments
6037             getLog().debug( CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" ) );
6038         }
6039 
6040         String cmdLine = null;
6041         if ( debug )
6042         {
6043             cmdLine = CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" );
6044 
6045             writeDebugJavadocScript( cmdLine, javadocOutputDirectory );
6046         }
6047 
6048         CommandLineUtils.StringStreamConsumer err = new JavadocUtil.JavadocOutputStreamConsumer();
6049         CommandLineUtils.StringStreamConsumer out = new JavadocUtil.JavadocOutputStreamConsumer();
6050         try
6051         {
6052             int exitCode = CommandLineUtils.executeCommandLine( cmd, out, err );
6053 
6054             String output = ( StringUtils.isEmpty( out.getOutput() ) ? null : '\n' + out.getOutput().trim() );
6055 
6056             if ( exitCode != 0 )
6057             {
6058                 if ( cmdLine == null )
6059                 {
6060                     cmdLine = CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" );
6061                 }
6062                 writeDebugJavadocScript( cmdLine, javadocOutputDirectory );
6063 
6064                 if ( StringUtils.isNotEmpty( output ) && StringUtils.isEmpty( err.getOutput() )
6065                     && isJavadocVMInitError( output ) )
6066                 {
6067                     throw new MavenReportException( output + '\n' + '\n' + JavadocUtil.ERROR_INIT_VM + '\n'
6068                         + "Or, try to reduce the Java heap size for the Javadoc goal using "
6069                         + "-Dminmemory=<size> and -Dmaxmemory=<size>." + '\n' + '\n' + "Command line was: " + cmdLine
6070                         + '\n' + '\n' + "Refer to the generated Javadoc files in '" + javadocOutputDirectory
6071                         + "' dir.\n" );
6072                 }
6073 
6074                 if ( StringUtils.isNotEmpty( output ) )
6075                 {
6076                     getLog().info( output );
6077                 }
6078 
6079                 StringBuilder msg = new StringBuilder( "\nExit code: " );
6080                 msg.append( exitCode );
6081                 if ( StringUtils.isNotEmpty( err.getOutput() ) )
6082                 {
6083                     msg.append( " - " ).append( err.getOutput() );
6084                 }
6085                 msg.append( '\n' );
6086                 msg.append( "Command line was: " ).append( cmdLine ).append( '\n' ).append( '\n' );
6087 
6088                 msg.append( "Refer to the generated Javadoc files in '" ).append( javadocOutputDirectory )
6089                 .append( "' dir.\n" );
6090 
6091                 throw new MavenReportException( msg.toString() );
6092             }
6093 
6094             if ( StringUtils.isNotEmpty( output ) )
6095             {
6096                 getLog().info( output );
6097             }
6098         }
6099         catch ( CommandLineException e )
6100         {
6101             throw new MavenReportException( "Unable to execute javadoc command: " + e.getMessage(), e );
6102         }
6103 
6104         // ----------------------------------------------------------------------
6105         // Handle Javadoc warnings
6106         // ----------------------------------------------------------------------
6107 
6108         if ( containsWarnings( err.getOutput() ) )
6109         {
6110             if ( getLog().isWarnEnabled() )
6111             {
6112                 getLog().warn( "Javadoc Warnings" );
6113 
6114                 StringTokenizer token = new StringTokenizer( err.getOutput(), "\n" );
6115                 while ( token.hasMoreTokens() )
6116                 {
6117                     String current = token.nextToken().trim();
6118 
6119                     getLog().warn( current );
6120                 }
6121             }
6122 
6123             if ( failOnWarnings )
6124             {
6125                 throw new MavenReportException( "Project contains Javadoc Warnings" );
6126             }
6127         }
6128     }
6129 
6130     private boolean containsWarnings( String output )
6131     {
6132         // JDK-8268774 / JDK-8270831
6133         if ( this.javadocRuntimeVersion.isBefore( "17" ) )
6134         {
6135             return StringUtils.isNotEmpty( output );
6136         }
6137         else
6138         {
6139             return Arrays.stream( output.split( "\\R" ) )
6140                             .reduce( ( first, second ) -> second ) // last line
6141                             .filter( line -> line.matches( "\\d+ warnings?" ) )
6142                             .isPresent();
6143         }
6144     }
6145 
6146     /**
6147      * Patches the given Javadoc output directory to work around CVE-2013-1571
6148      * (see http://www.kb.cert.org/vuls/id/225657).
6149      *
6150      * @param javadocOutputDirectory directory to scan for vulnerabilities
6151      * @param outputEncoding         encoding used by the javadoc tool (-docencoding parameter).
6152      *                               If {@code null}, the platform's default encoding is used (like javadoc does).
6153      * @return the number of patched files
6154      */
6155     private int fixFrameInjectionBug( File javadocOutputDirectory, String outputEncoding )
6156         throws IOException
6157     {
6158         final String fixData;
6159 
6160         try ( InputStream in = this.getClass().getResourceAsStream( "frame-injection-fix.txt" ) )
6161         {
6162             if ( in == null )
6163             {
6164                 throw new FileNotFoundException( "Missing resource 'frame-injection-fix.txt' in classpath." );
6165             }
6166             fixData = org.codehaus.plexus.util.StringUtils
6167                 .unifyLineSeparators( IOUtil.toString( in, "US-ASCII" ) ).trim();
6168         }
6169 
6170         final DirectoryScanner ds = new DirectoryScanner();
6171         ds.setBasedir( javadocOutputDirectory );
6172         ds.setCaseSensitive( false );
6173         ds.setIncludes( new String[]{ "**/index.html", "**/index.htm", "**/toc.html", "**/toc.htm" } );
6174         ds.addDefaultExcludes();
6175         ds.scan();
6176         int patched = 0;
6177         for ( String f : ds.getIncludedFiles() )
6178         {
6179             final File file = new File( javadocOutputDirectory, f );
6180             // we load the whole file as one String (toc/index files are
6181             // generally small, because they only contain frameset declaration):
6182             final String fileContents = FileUtils.fileRead( file, outputEncoding );
6183             // check if file may be vulnerable because it was not patched with "validURL(url)":
6184             if ( !StringUtils.contains( fileContents, "function validURL(url) {" ) )
6185             {
6186                 // we need to patch the file!
6187                 final String patchedFileContents =
6188                     StringUtils.replaceOnce( fileContents, "function loadFrames() {", fixData );
6189                 if ( !patchedFileContents.equals( fileContents ) )
6190                 {
6191                     FileUtils.fileWrite( file, outputEncoding, patchedFileContents );
6192                     patched++;
6193                 }
6194             }
6195         }
6196         return patched;
6197     }
6198 
6199     /**
6200      * @param outputFile        not nul
6201      * @param inputResourceName a not null resource in <code>src/main/java</code>, <code>src/main/resources</code> or
6202      *                          <code>src/main/javadoc</code> or in the Javadoc plugin dependencies.
6203      * @return the resource file absolute path as String
6204      * @since 2.6
6205      */
6206     private Optional<File> getResource( File outputFile, String inputResourceName )
6207     {
6208         if ( inputResourceName.startsWith( "/" ) )
6209         {
6210             inputResourceName = inputResourceName.replaceFirst( "//*", "" );
6211         }
6212 
6213         List<String> classPath = new ArrayList<>();
6214         classPath.add( project.getBuild().getSourceDirectory() );
6215 
6216         URL resourceURL = getResource( classPath, inputResourceName );
6217         if ( resourceURL != null )
6218         {
6219             getLog().debug( inputResourceName + " found in the main src directory of the project." );
6220             return Optional.of( FileUtils.toFile( resourceURL ) );
6221         }
6222 
6223         classPath.clear();
6224         List<Resource> resources = project.getBuild().getResources();
6225         for ( Resource resource : resources )
6226         {
6227             classPath.add( resource.getDirectory() );
6228         }
6229         resourceURL = getResource( classPath, inputResourceName );
6230         if ( resourceURL != null )
6231         {
6232             getLog().debug( inputResourceName + " found in the main resources directories of the project." );
6233             return Optional.of( FileUtils.toFile( resourceURL ) );
6234         }
6235 
6236         if ( javadocDirectory.exists() )
6237         {
6238             classPath.clear();
6239             classPath.add( javadocDirectory.getAbsolutePath() );
6240             resourceURL = getResource( classPath, inputResourceName );
6241             if ( resourceURL != null )
6242             {
6243                 getLog().debug( inputResourceName + " found in the main javadoc directory of the project." );
6244                 return Optional.of( FileUtils.toFile( resourceURL ) );
6245             }
6246         }
6247 
6248         classPath.clear();
6249         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
6250         Plugin javadocPlugin = getPlugin( project, pluginId );
6251         if ( javadocPlugin != null && javadocPlugin.getDependencies() != null )
6252         {
6253             List<Dependency> dependencies = javadocPlugin.getDependencies();
6254             for ( Dependency dependency : dependencies )
6255             {
6256                 ResourcesArtifact resourceArtifact = new ResourcesArtifact();
6257                 resourceArtifact.setGroupId( dependency.getGroupId() );
6258                 resourceArtifact.setArtifactId( dependency.getArtifactId() );
6259                 resourceArtifact.setVersion( dependency.getVersion() );
6260                 resourceArtifact.setClassifier( dependency.getClassifier() );
6261                 Artifact artifact = null;
6262                 try
6263                 {
6264                     artifact = createAndResolveArtifact( resourceArtifact );
6265                 }
6266                 catch ( Exception e )
6267                 {
6268                     logError( "Unable to retrieve the dependency: " + dependency + ". Ignored.", e );
6269                 }
6270 
6271                 if ( artifact != null && artifact.getFile().exists() )
6272                 {
6273                     classPath.add( artifact.getFile().getAbsolutePath() );
6274                 }
6275             }
6276             resourceURL = getResource( classPath, inputResourceName );
6277             if ( resourceURL != null )
6278             {
6279                 getLog().debug( inputResourceName + " found in javadoc plugin dependencies." );
6280                 try
6281                 {
6282                     JavadocUtil.copyResource( resourceURL, outputFile );
6283 
6284                     return Optional.of( outputFile );
6285                 }
6286                 catch ( IOException e )
6287                 {
6288                     logError( "IOException: " + e.getMessage(), e );
6289                 }
6290             }
6291         }
6292 
6293         getLog().warn( "Unable to find the resource '" + inputResourceName + "'. Using default Javadoc resources." );
6294 
6295         return Optional.empty();
6296     }
6297 
6298     /**
6299      * @param classPath a not null String list of files where resource will be looked up
6300      * @param resource a not null resource to find in the class path
6301      * @return the resource from the given classpath or null if not found
6302      * @see ClassLoader#getResource(String)
6303      * @since 2.6
6304      */
6305     private URL getResource( final List<String> classPath, final String resource )
6306     {
6307         List<URL> urls = new ArrayList<>( classPath.size() );
6308         for ( String filename : classPath )
6309         {
6310             try
6311             {
6312                 urls.add( new File( filename ).toURI().toURL() );
6313             }
6314             catch ( MalformedURLException e )
6315             {
6316                 getLog().error( "MalformedURLException: " + e.getMessage() );
6317             }
6318         }
6319 
6320         URLClassLoader javadocClassLoader = new URLClassLoader( urls.toArray( new URL[urls.size()] ), null );
6321         try
6322         {
6323             return javadocClassLoader.getResource( resource );
6324         }
6325         finally
6326         {
6327             try
6328             {
6329                 javadocClassLoader.close();
6330             }
6331             catch ( IOException ex )
6332             {
6333                 // ignore
6334             }
6335         }
6336     }
6337 
6338     /**
6339      * Get the full javadoc goal. Loads the plugin's pom.properties to get the current plugin version.
6340      *
6341      * @return <code>org.apache.maven.plugins:maven-javadoc-plugin:CURRENT_VERSION:[test-]javadoc</code>
6342      */
6343     private String getFullJavadocGoal()
6344     {
6345         String javadocPluginVersion = null;
6346         String resource = "META-INF/maven/org.apache.maven.plugins/maven-javadoc-plugin/pom.properties";
6347         try ( InputStream resourceAsStream 
6348                         = AbstractJavadocMojo.class.getClassLoader().getResourceAsStream( resource ) )
6349         {
6350             if ( resourceAsStream != null )
6351             {
6352                 Properties properties = new Properties();
6353                 properties.load( resourceAsStream );
6354                 if ( StringUtils.isNotEmpty( properties.getProperty( "version" ) ) )
6355                 {
6356                     javadocPluginVersion = properties.getProperty( "version" );
6357                 }
6358             }
6359         }
6360         catch ( IOException e )
6361         {
6362             // nop
6363         }
6364 
6365         StringBuilder sb = new StringBuilder();
6366 
6367         sb.append( "org.apache.maven.plugins:maven-javadoc-plugin:" );
6368         if ( StringUtils.isNotEmpty( javadocPluginVersion ) )
6369         {
6370             sb.append( javadocPluginVersion ).append( ":" );
6371         }
6372 
6373         if ( this instanceof TestJavadocReport )
6374         {
6375             sb.append( "test-javadoc" );
6376         }
6377         else
6378         {
6379             sb.append( "javadoc" );
6380         }
6381 
6382         return sb.toString();
6383     }
6384 
6385     /**
6386      * Using Maven, a Javadoc link is given by <code>${project.url}/apidocs</code>.
6387      *
6388      * @return the detected Javadoc links using the Maven conventions for all modules defined in the current project
6389      *         or an empty list
6390      * @throws MavenReportException if any
6391      * @see #detectOfflineLinks
6392      * @see #reactorProjects
6393      * @since 2.6
6394      */
6395     private List<OfflineLink> getModulesLinks()
6396         throws MavenReportException
6397     {
6398         List<MavenProject> aggregatedProjects = reactorProjects;
6399         if ( !detectOfflineLinks || isAggregator() || aggregatedProjects.isEmpty() )
6400         {
6401             return Collections.emptyList();
6402         }
6403 
6404         getLog().debug( "Trying to add links for modules..." );
6405 
6406         Set<String> dependencyArtifactIds = new HashSet<>();
6407         final Set<Artifact> dependencyArtifacts = project.getDependencyArtifacts();
6408         for ( Artifact artifact : dependencyArtifacts )
6409         {
6410             dependencyArtifactIds.add( artifact.getId() );
6411         }
6412 
6413         List<OfflineLink> modulesLinks = new ArrayList<>();
6414         String javadocDirRelative = PathUtils.toRelative( project.getBasedir(), getOutputDirectory() );
6415         for ( MavenProject p : aggregatedProjects )
6416         {
6417             if ( !dependencyArtifactIds.contains( p.getArtifact().getId() ) || ( p.getUrl() == null ) )
6418             {
6419                 continue;
6420             }
6421 
6422             File location = new File( p.getBasedir(), javadocDirRelative );
6423 
6424             if ( !location.exists() )
6425             {
6426                 if ( getLog().isDebugEnabled() )
6427                 {
6428                     getLog().debug( "Javadoc directory not found: " + location );
6429                 }
6430 
6431                 String javadocGoal = getFullJavadocGoal();
6432                 getLog().info(
6433                     "The goal '" + javadocGoal + "' has not been previously called for the module: '" + p.getId()
6434                         + "'. Trying to invoke it..." );
6435 
6436                 File invokerDir = new File( project.getBuild().getDirectory(), "invoker" );
6437                 invokerDir.mkdirs();
6438                 File invokerLogFile = FileUtils.createTempFile( "maven-javadoc-plugin", ".txt", invokerDir );
6439                 try
6440                 {
6441                     JavadocUtil.invokeMaven( getLog(), new File( localRepository.getBasedir() ), p.getFile(),
6442                                              Collections.singletonList( javadocGoal ), null, invokerLogFile,
6443                                              session.getRequest().getGlobalSettingsFile() );
6444                 }
6445                 catch ( MavenInvocationException e )
6446                 {
6447                     logError( "MavenInvocationException: " + e.getMessage(), e );
6448 
6449                     String invokerLogContent = JavadocUtil.readFile( invokerLogFile, null /* platform encoding */ );
6450 
6451                     // TODO: Why are we only interested in cases where the JVM won't start?
6452                     // [MJAVADOC-275][jdcasey] I changed the logic here to only throw an error WHEN
6453                     //   the JVM won't start (opposite of what it was).
6454                     if ( invokerLogContent != null && invokerLogContent.contains( JavadocUtil.ERROR_INIT_VM ) )
6455                     {
6456                         throw new MavenReportException( e.getMessage(), e );
6457                     }
6458                 }
6459                 finally
6460                 {
6461                     // just create the directory to prevent repeated invocations..
6462                     if ( !location.exists() )
6463                     {
6464                         getLog().warn( "Creating fake javadoc directory to prevent repeated invocations: " + location );
6465                         location.mkdirs();
6466                     }
6467                 }
6468             }
6469 
6470             if ( location.exists() )
6471             {
6472                 String url = getJavadocLink( p );
6473 
6474                 OfflineLink ol = new OfflineLink();
6475                 ol.setUrl( url );
6476                 ol.setLocation( location.getAbsolutePath() );
6477 
6478                 if ( getLog().isDebugEnabled() )
6479                 {
6480                     getLog().debug( "Added Javadoc offline link: " + url + " for the module: " + p.getId() );
6481                 }
6482 
6483                 modulesLinks.add( ol );
6484             }
6485         }
6486 
6487         return modulesLinks;
6488     }
6489 
6490     /**
6491      * Using Maven, a Javadoc link is given by <code>${project.url}/apidocs</code>.
6492      *
6493      * @return the detected Javadoc links using the Maven conventions for all dependencies defined in the current
6494      *         project or an empty list.
6495      * @see #detectLinks
6496      * @see #isValidJavadocLink
6497      * @since 2.6
6498      */
6499     private List<String> getDependenciesLinks()
6500     {
6501         if ( !detectLinks )
6502         {
6503             return Collections.emptyList();
6504         }
6505 
6506         getLog().debug( "Trying to add links for dependencies..." );
6507 
6508         List<String> dependenciesLinks = new ArrayList<>();
6509 
6510         final Set<Artifact> dependencies = project.getDependencyArtifacts();
6511         for ( Artifact artifact : dependencies )
6512         {
6513             if ( artifact.getFile() == null || !artifact.getFile().exists() )
6514             {
6515                 continue;
6516             }
6517 
6518             Optional<DependencyLink> depLink =
6519                 this.dependencyLinks.stream().filter( d -> matches( d, artifact ) ).findAny();
6520             
6521             final String url;
6522             final boolean detected;
6523             if ( depLink.isPresent() )
6524             {
6525                 url = depLink.get().getUrl();
6526                 detected = false;
6527             }
6528             else
6529             {
6530                 try
6531                 {
6532                     MavenProject artifactProject =
6533                         mavenProjectBuilder.build( artifact, getProjectBuildingRequest( project ) ).getProject();
6534 
6535                     url = getJavadocLink( artifactProject );
6536                     detected = true;
6537                 }
6538                 catch ( ProjectBuildingException e )
6539                 {
6540                     logError( "ProjectBuildingException for " + artifact.toString() + ": " + e.getMessage(), e );
6541                     continue;
6542                 }
6543             }
6544             
6545             if ( isValidJavadocLink( url, detected ) )
6546             {
6547                 getLog().debug( "Added Javadoc link: " + url + " for " + artifact.getId() );
6548 
6549                 dependenciesLinks.add( url );
6550             }
6551         }
6552 
6553         return dependenciesLinks;
6554     }
6555 
6556     private boolean matches( DependencyLink d, Artifact artifact )
6557     {
6558         if ( d.getGroupId() != null && !d.getGroupId().equals( artifact.getGroupId() ) )
6559         {
6560             return false;
6561         }
6562         if ( d.getArtifactId() != null && !d.getArtifactId().equals( artifact.getArtifactId() ) )
6563         {
6564             return false;
6565         }
6566         if ( d.getClassifier() != null && !d.getClassifier().equals( artifact.getClassifier() ) )
6567         {
6568             return false;
6569         }
6570         return true;
6571     }
6572 
6573     /**
6574      * @return if {@link #detectJavaApiLink}, the Java API link based on the {@link #javaApiLinks} properties and the
6575      *         value of the <code>source</code> parameter in the
6576      *         <code>org.apache.maven.plugins:maven-compiler-plugin</code>
6577      *         defined in <code>${project.build.plugins}</code> or in <code>${project.build.pluginManagement}</code>,
6578      *         or the {@link #javadocRuntimeVersion}, or <code>null</code> if not defined.
6579      * @see #detectJavaApiLink
6580      * @see #javaApiLinks
6581      * @see <a href="http://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#source">source parameter</a>
6582      * @since 2.6
6583      */
6584     protected final OfflineLink getDefaultJavadocApiLink()
6585     {
6586         if ( !detectJavaApiLink )
6587         {
6588             return null;
6589         }
6590 
6591         final JavaVersion javaApiversion;
6592         if ( release != null )
6593         {
6594             javaApiversion = JavaVersion.parse( release );
6595         }
6596         else if ( source != null && !source.isEmpty() )
6597         {
6598             javaApiversion = JavaVersion.parse( source );
6599         }
6600         else
6601         {
6602             final String pluginId = "org.apache.maven.plugins:maven-compiler-plugin";
6603             String sourceConfigured = getPluginParameter( project, pluginId, "source" );
6604             if ( sourceConfigured != null )
6605             {
6606                 javaApiversion = JavaVersion.parse( sourceConfigured );
6607             }
6608             else
6609             {
6610                 getLog().debug( "No maven-compiler-plugin defined in ${build.plugins} or in "
6611                                     + "${project.build.pluginManagement} for the " + project.getId()
6612                                     + ". Added Javadoc API link according the javadoc executable version i.e.: "
6613                                     + javadocRuntimeVersion );
6614 
6615                 javaApiversion = javadocRuntimeVersion;
6616             }
6617         }
6618 
6619         final String javaApiKey;
6620         if ( javaApiversion.asMajor().isAtLeast( "9" ) )
6621         {
6622             javaApiKey = "api_" + javaApiversion.asMajor();
6623         }
6624         else
6625         {
6626             javaApiKey = "api_1." + javaApiversion.asMajor().toString().charAt( 0 );
6627         }
6628 
6629         final String javaApiLink;
6630         if ( javaApiLinks != null && javaApiLinks.containsKey( javaApiKey ) )
6631         {
6632             javaApiLink = javaApiLinks.getProperty( javaApiKey );
6633         }
6634         else if ( javaApiversion.isAtLeast( "16" ) )
6635         {
6636             javaApiLink = null; // JDK-8216497
6637         }
6638         else if ( javaApiversion.isAtLeast( "11" ) )
6639         {
6640             javaApiLink =
6641                 String.format( "https://docs.oracle.com/en/java/javase/%s/docs/api/", javaApiversion.getValue( 1 ) );
6642         }
6643         else if ( javaApiversion.asMajor().isAtLeast( "6" ) )
6644         {
6645             javaApiLink =
6646                 String.format( "https://docs.oracle.com/javase/%s/docs/api/", javaApiversion.asMajor().getValue( 1 ) );
6647         }
6648         else if ( javaApiversion.isAtLeast( "1.5" ) )
6649         {
6650             javaApiLink = "https://docs.oracle.com/javase/1.5.0/docs/api/";
6651         }
6652         else
6653         {
6654             javaApiLink = null;
6655         }
6656 
6657         if ( getLog().isDebugEnabled() )
6658         {
6659             if ( javaApiLink != null )
6660             {
6661                 getLog().debug( "Found Java API link: " + javaApiLink );
6662             }
6663             else
6664             {
6665                 getLog().debug( "No Java API link found." );
6666             }
6667         }
6668 
6669         if ( javaApiLink == null )
6670         {
6671             return null;
6672         }
6673 
6674         final Path javaApiListFile;
6675         final String resourceName;
6676         if ( javaApiversion.isAtLeast( "10" ) )
6677         {
6678             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve( "element-list" );
6679             resourceName = "java-api-element-list-" + javaApiversion.toString().substring( 0, 2 );
6680         }
6681         else if ( javaApiversion.asMajor().isAtLeast( "9" ) )
6682         {
6683             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve( "package-list" );
6684             resourceName = "java-api-package-list-9";
6685         }
6686         else
6687         {
6688             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve( "package-list" );
6689             resourceName = "java-api-package-list-1." + javaApiversion.asMajor().toString().charAt( 0 );
6690         }
6691 
6692         OfflineLink link = new OfflineLink();
6693         link.setLocation( javaApiListFile.getParent().toAbsolutePath().toString() );
6694         link.setUrl( javaApiLink );
6695 
6696         InputStream in = this.getClass().getResourceAsStream( resourceName );
6697         if ( in != null )
6698         {
6699             try ( InputStream closableIS = in )
6700             {
6701                 // TODO only copy when changed
6702                 Files.copy( closableIS, javaApiListFile, StandardCopyOption.REPLACE_EXISTING );
6703             }
6704             catch ( IOException ioe )
6705             {
6706                 logError( "Can't get " + resourceName + ": " + ioe.getMessage(), ioe );
6707                 return null;
6708             }
6709         }
6710 
6711         return link;
6712     }
6713 
6714     /**
6715      * Follows all of the given links, and returns their last redirect locations. Ordering is kept.
6716      * This is necessary because javadoc tool doesn't follow links, see JDK-8190312 (MJAVADOC-427, MJAVADOC-487)
6717      *
6718      * @param links Links to follow.
6719      * @return Last redirect location of all the links.
6720      */
6721     private Set<String> followLinks( Set<String> links )
6722     {
6723         Set<String> redirectLinks = new LinkedHashSet<>( links.size() );
6724         for ( String link : links )
6725         {
6726             try
6727             {
6728                 redirectLinks.add( JavadocUtil.getRedirectUrl( new URI( link ).toURL(), settings ).toString() );
6729             }
6730             catch ( Exception e )
6731             {
6732                 // only print in debug, it should have been logged already in warn/error because link isn't valid
6733                 getLog().debug( "Could not follow " + link + ". Reason: " + e.getMessage() );
6734 
6735                 // Even when link produces error it should be kept in the set because the error might be caused by
6736                 // incomplete redirect configuration on the server side.
6737                 // This partially restores the previous behaviour before fix for MJAVADOC-427
6738                 redirectLinks.add( link );
6739             }
6740         }
6741         return redirectLinks;
6742     }
6743 
6744     /**
6745      * @param link not null
6746      * @param detecting <code>true</code> if the link is generated by
6747      * <code>detectLinks</code>, or <code>false</code> otherwise
6748      * @return <code>true</code> if the link has a <code>/package-list</code>, <code>false</code> otherwise.
6749      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#package-list">
6750      *      package-list spec</a>
6751      * @since 2.6
6752      */
6753     protected boolean isValidJavadocLink( String link, boolean detecting )
6754     {
6755         try
6756         {
6757             final URI packageListUri;
6758             final URI elementListUri;
6759 
6760             if ( link.trim().toLowerCase( Locale.ENGLISH ).startsWith( "http:" ) || link.trim().toLowerCase(
6761                 Locale.ENGLISH ).startsWith( "https:" ) || link.trim().toLowerCase( Locale.ENGLISH ).startsWith(
6762                 "ftp:" ) || link.trim().toLowerCase( Locale.ENGLISH ).startsWith( "file:" ) )
6763             {
6764                 packageListUri = new URI( link + '/' + PACKAGE_LIST );
6765                 elementListUri = new URI( link + '/' + ELEMENT_LIST );
6766             }
6767             else
6768             {
6769                 // links can be relative paths or files
6770                 File dir = new File( link );
6771                 if ( !dir.isAbsolute() )
6772                 {
6773                     dir = new File( getOutputDirectory(), link );
6774                 }
6775                 if ( !dir.isDirectory() )
6776                 {
6777                     if ( detecting )
6778                     {
6779                         getLog().warn( "The given File link: " + dir + " is not a dir." );
6780                     }
6781                     else
6782                     {
6783                         getLog().error( "The given File link: " + dir + " is not a dir." );
6784                     }
6785                 }
6786                 packageListUri = new File( dir, PACKAGE_LIST ).toURI();
6787                 elementListUri = new File( dir, ELEMENT_LIST ).toURI();
6788             }
6789 
6790 
6791             try
6792             {
6793                 if ( JavadocUtil.isValidElementList( elementListUri.toURL(), settings, validateLinks ) )
6794                 {
6795                     return true;
6796                 }
6797             }
6798             catch ( IOException e )
6799             {
6800             }
6801 
6802             if ( JavadocUtil.isValidPackageList( packageListUri.toURL(), settings, validateLinks ) )
6803             {
6804                 return true;
6805             }
6806 
6807             if ( getLog().isErrorEnabled() )
6808             {
6809                 if ( detecting )
6810                 {
6811                     getLog().warn( "Invalid links: "
6812                                     + link + " with /" + PACKAGE_LIST + " or / " + ELEMENT_LIST + ". Ignored it." );
6813                 }
6814                 else
6815                 {
6816                     getLog().error( "Invalid links: "
6817                                     + link + " with /" + PACKAGE_LIST + " or / " + ELEMENT_LIST + ". Ignored it." );
6818                 }
6819             }
6820 
6821             return false;
6822         }
6823         catch ( URISyntaxException e )
6824         {
6825             if ( getLog().isErrorEnabled() )
6826             {
6827                 if ( detecting )
6828                 {
6829                     getLog().warn( "Malformed link: " + e.getInput() + ". Ignored it." );
6830                 }
6831                 else
6832                 {
6833                     getLog().error( "Malformed link: " + e.getInput() + ". Ignored it." );
6834                 }
6835             }
6836             return false;
6837         }
6838         catch ( IOException e )
6839         {
6840             if ( getLog().isErrorEnabled() )
6841             {
6842                 if ( detecting )
6843                 {
6844                     getLog().warn( "Error fetching link: " + link + ". Ignored it." );
6845                 }
6846                 else
6847                 {
6848                     getLog().error( "Error fetching link: " + link + ". Ignored it." );
6849                 }
6850             }
6851             return false;
6852         }
6853     }
6854 
6855     /**
6856      * Write a debug javadoc script in case of command line error or in debug mode.
6857      *
6858      * @param cmdLine                the current command line as string, not null.
6859      * @param javadocOutputDirectory the output dir, not null.
6860      * @see #executeJavadocCommandLine(Commandline, File)
6861      * @since 2.6
6862      */
6863     private void writeDebugJavadocScript( String cmdLine, File javadocOutputDirectory )
6864     {
6865         File commandLineFile = new File( javadocOutputDirectory, DEBUG_JAVADOC_SCRIPT_NAME );
6866         commandLineFile.getParentFile().mkdirs();
6867 
6868         try
6869         {
6870             FileUtils.fileWrite( commandLineFile.getAbsolutePath(), null /* platform encoding */, cmdLine );
6871 
6872             if ( !SystemUtils.IS_OS_WINDOWS )
6873             {
6874                 Runtime.getRuntime().exec( new String[]{ "chmod", "a+x", commandLineFile.getAbsolutePath() } );
6875             }
6876         }
6877         catch ( IOException e )
6878         {
6879             logError( "Unable to write '" + commandLineFile.getName() + "' debug script file", e );
6880         }
6881     }
6882 
6883     /**
6884      * Check if the Javadoc JVM is correctly started or not.
6885      *
6886      * @param output the command line output, not null.
6887      * @return <code>true</code> if Javadoc output command line contains Javadoc word, <code>false</code> otherwise.
6888      * @see #executeJavadocCommandLine(Commandline, File)
6889      * @since 2.6.1
6890      */
6891     private boolean isJavadocVMInitError( String output )
6892     {
6893         /*
6894          * see main.usage and main.Building_tree keys from
6895          * com.sun.tools.javadoc.resources.javadoc bundle in tools.jar
6896          */
6897         return !( output.contains( "Javadoc" ) || output.contains( "javadoc" ) );
6898     }
6899 
6900     // ----------------------------------------------------------------------
6901     // Static methods
6902     // ----------------------------------------------------------------------
6903 
6904     /**
6905      * @param p not null
6906      * @return the javadoc link based on the project url i.e. <code>${project.url}/${destDir}</code> where
6907      *         <code>destDir</code> is configued in the Javadoc plugin configuration (<code>apidocs</code> by default).
6908      * @since 2.6
6909      */
6910     private static String getJavadocLink( MavenProject p )
6911     {
6912         if ( p.getUrl() == null )
6913         {
6914             return null;
6915         }
6916 
6917         String url = cleanUrl( p.getUrl() );
6918         String destDir = "apidocs"; // see JavadocReport#destDir
6919 
6920         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
6921         String destDirConfigured = getPluginParameter( p, pluginId, "destDir" );
6922         if ( destDirConfigured != null )
6923         {
6924             destDir = destDirConfigured;
6925         }
6926 
6927         return url + "/" + destDir;
6928     }
6929 
6930     /**
6931      * @param url could be null.
6932      * @return the url cleaned or empty if url was null.
6933      * @since 2.6
6934      */
6935     private static String cleanUrl( String url )
6936     {
6937         if ( url == null )
6938         {
6939             return "";
6940         }
6941 
6942         url = url.trim();
6943         while ( url.endsWith( "/" ) )
6944         {
6945             url = url.substring( 0, url.lastIndexOf( "/" ) );
6946         }
6947 
6948         return url;
6949     }
6950 
6951     /**
6952      * @param p        not null
6953      * @param pluginId not null key of the plugin defined in {@link org.apache.maven.model.Build#getPluginsAsMap()}
6954      *                 or in {@link org.apache.maven.model.PluginManagement#getPluginsAsMap()}
6955      * @return the Maven plugin defined in <code>${project.build.plugins}</code> or in
6956      *         <code>${project.build.pluginManagement}</code>, or <code>null</code> if not defined.
6957      * @since 2.6
6958      */
6959     private static Plugin getPlugin( MavenProject p, String pluginId )
6960     {
6961         if ( ( p.getBuild() == null ) || ( p.getBuild().getPluginsAsMap() == null ) )
6962         {
6963             return null;
6964         }
6965 
6966         Plugin plugin = p.getBuild().getPluginsAsMap().get( pluginId );
6967 
6968         if ( ( plugin == null ) && ( p.getBuild().getPluginManagement() != null ) && (
6969             p.getBuild().getPluginManagement().getPluginsAsMap() != null ) )
6970         {
6971             plugin = p.getBuild().getPluginManagement().getPluginsAsMap().get( pluginId );
6972         }
6973 
6974         return plugin;
6975     }
6976 
6977     /**
6978      * @param p        not null
6979      * @param pluginId not null
6980      * @param param    not null
6981      * @return the simple parameter as String defined in the plugin configuration by <code>param</code> key
6982      *         or <code>null</code> if not found.
6983      * @since 2.6
6984      */
6985     private static String getPluginParameter( MavenProject p, String pluginId, String param )
6986     {
6987 //        p.getGoalConfiguration( pluginGroupId, pluginArtifactId, executionId, goalId );
6988         Plugin plugin = getPlugin( p, pluginId );
6989         if ( plugin != null )
6990         {
6991             Xpp3Dom xpp3Dom = (Xpp3Dom) plugin.getConfiguration();
6992             if ( xpp3Dom != null && xpp3Dom.getChild( param ) != null
6993                 && StringUtils.isNotEmpty( xpp3Dom.getChild( param ).getValue() ) )
6994             {
6995                 return xpp3Dom.getChild( param ).getValue();
6996             }
6997         }
6998 
6999         return null;
7000     }
7001 
7002     /**
7003      * Construct the output file for the generated javadoc-options XML file, after creating the
7004      * javadocOptionsDir if necessary. This method does NOT write to the file in question.
7005      *
7006      * @return The options {@link File} file.
7007      * @since 2.7
7008      */
7009     protected final File getJavadocOptionsFile()
7010     {
7011         if ( javadocOptionsDir != null && !javadocOptionsDir.exists() )
7012         {
7013             javadocOptionsDir.mkdirs();
7014         }
7015 
7016         return new File( javadocOptionsDir, "javadoc-options-" + getAttachmentClassifier() + ".xml" );
7017     }
7018 
7019     /**
7020      * Generate a javadoc-options XML file, for either bundling with a javadoc-resources artifact OR
7021      * supplying to a distro module in a includeDependencySources configuration, so the javadoc options
7022      * from this execution can be reconstructed and merged in the distro build.
7023      *
7024      * @return {@link JavadocOptions}
7025      * @throws IOException {@link IOException}
7026      * @since 2.7
7027      */
7028     protected final JavadocOptions buildJavadocOptions()
7029         throws IOException
7030     {
7031         JavadocOptions options = new JavadocOptions();
7032 
7033         options.setBootclasspathArtifacts( toList( bootclasspathArtifacts ) );
7034         options.setDocfilesSubdirsUsed( docfilessubdirs );
7035         options.setDocletArtifacts( toList( docletArtifact, docletArtifacts ) );
7036         options.setExcludedDocfilesSubdirs( excludedocfilessubdir );
7037         options.setExcludePackageNames( toList( excludePackageNames ) );
7038         options.setGroups( toList( groups ) );
7039         options.setLinks( links );
7040         options.setOfflineLinks( toList( offlineLinks ) );
7041         options.setResourcesArtifacts( toList( resourcesArtifacts ) );
7042         options.setTagletArtifacts( toList( tagletArtifact, tagletArtifacts ) );
7043         options.setTaglets( toList( taglets ) );
7044         options.setTags( toList( tags ) );
7045 
7046         if ( getProject() != null && getJavadocDirectory() != null )
7047         {
7048             options.setJavadocResourcesDirectory(
7049                 toRelative( getProject().getBasedir(), getJavadocDirectory().getAbsolutePath() ) );
7050         }
7051 
7052         File optionsFile = getJavadocOptionsFile();
7053 
7054         try ( Writer writer = WriterFactory.newXmlWriter( optionsFile ) )
7055         {
7056             new JavadocOptionsXpp3Writer().write( writer, options );
7057         }
7058 
7059         return options;
7060     }
7061 
7062     /**
7063      * Override this if you need to provide a bundle attachment classifier, as in the case of test
7064      * javadocs.
7065      * @return The attachment classifier.
7066      */
7067     protected String getAttachmentClassifier()
7068     {
7069         return JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER;
7070     }
7071 
7072     /**
7073      * Logs an error with throwable content only if in debug.
7074      *
7075      * @param message The message which should be announced.
7076      * @param t The throwable part of the message.
7077      */
7078     protected void logError( String message, Throwable t )
7079     {
7080         if ( getLog().isDebugEnabled() )
7081         {
7082             getLog().error( message, t );
7083         }
7084         else
7085         {
7086             getLog().error( message );
7087         }
7088     }
7089 
7090     /**
7091      * @param prefix The prefix of the exception.
7092      * @param e The exception.
7093      * @throws MojoExecutionException {@link MojoExecutionException} issue while generating report
7094      */
7095     protected void failOnError( String prefix, Exception e )
7096         throws MojoExecutionException
7097     {
7098         if ( failOnError )
7099         {
7100             if ( e instanceof RuntimeException )
7101             {
7102                 throw (RuntimeException) e;
7103             }
7104             throw new MojoExecutionException( prefix + ": " + e.getMessage(), e );
7105         }
7106 
7107         getLog().error( prefix + ": " + e.getMessage(), e );
7108     }
7109 
7110 
7111     /**
7112      *
7113      * @return List of projects to be part of aggregated javadoc
7114      */
7115     private List<MavenProject> getAggregatedProjects()
7116     {
7117         if ( this.reactorProjects == null )
7118         {
7119            return Collections.emptyList();
7120         }
7121         Map<Path, MavenProject> reactorProjectsMap = new HashMap<>();
7122         for ( MavenProject reactorProject : this.reactorProjects )
7123         {
7124             if ( !isSkippedJavadoc( reactorProject ) && //
7125                     !isSkippedModule( reactorProject ) )
7126             {
7127                 reactorProjectsMap.put( reactorProject.getBasedir().toPath(), reactorProject );
7128             }
7129         }
7130 
7131         return new ArrayList<>( modulesForAggregatedProject( project, reactorProjectsMap ) );
7132     }
7133 
7134     /**
7135      *
7136      * @return <code>true</code> if the module need to be skipped from aggregate generation
7137      */
7138     protected boolean isSkippedModule( MavenProject mavenProject )
7139     {
7140         if ( StringUtils.isEmpty( this.skippedModules ) )
7141         {
7142             return false;
7143         }
7144         List<String> modulesToSkip = Arrays.asList( StringUtils.split( this.skippedModules, ',' ) );
7145         return modulesToSkip.contains( mavenProject.getArtifactId() );
7146     }
7147 
7148     /**
7149      *
7150      * @return <code>true</code> if the pom configuration skip javadoc generation for the project
7151      */
7152     protected boolean isSkippedJavadoc( MavenProject mavenProject )
7153     {
7154         String property = mavenProject.getProperties().getProperty( "maven.javadoc.skip" );
7155         if ( property != null )
7156         {
7157             boolean skip = BooleanUtils.toBoolean( property );
7158             getLog().debug( "isSkippedJavadoc " + mavenProject + " " + skip );
7159             return skip;
7160         }
7161         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
7162         property = getPluginParameter( mavenProject, pluginId, "skip" );
7163         if ( property != null )
7164         {
7165             boolean skip = BooleanUtils.toBoolean( property );
7166             getLog().debug( "isSkippedJavadoc " + mavenProject + " " + skip );
7167             return skip;
7168         }
7169         if ( mavenProject.getParent() != null )
7170         {
7171             return isSkippedJavadoc( mavenProject.getParent() );
7172         }
7173         getLog().debug( "isSkippedJavadoc " + mavenProject + " " + false );
7174         return false;
7175     }
7176 
7177 }