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