View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.javadoc;
20  
21  import java.io.File;
22  import java.nio.file.Path;
23  import java.util.Collection;
24  import java.util.Locale;
25  import java.util.Map;
26  import java.util.ResourceBundle;
27  import java.util.stream.Collectors;
28  
29  import org.apache.maven.doxia.sink.Sink;
30  import org.apache.maven.doxia.sink.SinkFactory;
31  import org.apache.maven.doxia.siterenderer.RenderingContext;
32  import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugin.MojoFailureException;
35  import org.apache.maven.plugins.annotations.Execute;
36  import org.apache.maven.plugins.annotations.LifecyclePhase;
37  import org.apache.maven.plugins.annotations.Mojo;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.plugins.annotations.ResolutionScope;
40  import org.apache.maven.reporting.MavenMultiPageReport;
41  import org.apache.maven.reporting.MavenReportException;
42  import org.codehaus.plexus.util.StringUtils;
43  
44  /**
45   * Generates documentation for the <code>Java code</code> in an <b>NON aggregator</b> project using the standard
46   * <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html">Javadoc Tool</a>.
47   *
48   * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
49   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
50   * @since 2.0
51   * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html">Javadoc Tool</a>
52   */
53  @Mojo(name = "javadoc", requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true)
54  @Execute(phase = LifecyclePhase.GENERATE_SOURCES)
55  public class JavadocReport extends AbstractJavadocMojo implements MavenMultiPageReport {
56      // ----------------------------------------------------------------------
57      // Report Mojo Parameters
58      // ----------------------------------------------------------------------
59  
60      /**
61       * Specifies the destination directory where javadoc saves the generated HTML files.
62       */
63      @Parameter(
64              property = "reportOutputDirectory",
65              defaultValue = "${project.reporting.outputDirectory}/apidocs",
66              required = true)
67      private File reportOutputDirectory;
68  
69      /**
70       * The name of the destination directory.
71       * <br/>
72       *
73       * @since 2.1
74       */
75      @Parameter(property = "destDir", defaultValue = "apidocs")
76      private String destDir;
77  
78      /**
79       * The name of the Javadoc report to be displayed in the Maven Generated Reports page
80       * (i.e. <code>project-reports.html</code>).
81       *
82       * @since 2.1
83       */
84      @Parameter(property = "name")
85      private String name;
86  
87      /**
88       * The description of the Javadoc report to be displayed in the Maven Generated Reports page
89       * (i.e. <code>project-reports.html</code>).
90       *
91       * @since 2.1
92       */
93      @Parameter(property = "description")
94      private String description;
95  
96      // ----------------------------------------------------------------------
97      // Report public methods
98      // ----------------------------------------------------------------------
99  
100     /** {@inheritDoc} */
101     @Override
102     public String getName(Locale locale) {
103         if (StringUtils.isEmpty(name)) {
104             return getBundle(locale).getString("report.javadoc.name");
105         }
106 
107         return name;
108     }
109 
110     /** {@inheritDoc} */
111     @Override
112     public String getDescription(Locale locale) {
113         if (StringUtils.isEmpty(description)) {
114             return getBundle(locale).getString("report.javadoc.description");
115         }
116 
117         return description;
118     }
119 
120     /** {@inheritDoc} */
121     @Override
122     public void generate(org.codehaus.doxia.sink.Sink sink, Locale locale) throws MavenReportException {
123         generate(sink, null, locale);
124     }
125 
126     public void generate(Sink sink, Locale locale) throws MavenReportException {
127         generate(sink, null, locale);
128     }
129 
130     /** {@inheritDoc} */
131     @Override
132     public void generate(Sink sink, SinkFactory sinkFactory, Locale locale) throws MavenReportException {
133         outputDirectory = getReportOutputDirectory();
134 
135         try {
136             executeReport(locale);
137         } catch (MavenReportException | RuntimeException e) {
138             if (failOnError) {
139                 throw e;
140             }
141             getLog().error("Error while creating javadoc report: " + e.getMessage(), e);
142         }
143     }
144 
145     /** {@inheritDoc} */
146     @Override
147     public String getOutputName() {
148         return destDir + "/index";
149     }
150 
151     /** {@inheritDoc} */
152     @Override
153     public boolean isExternalReport() {
154         return true;
155     }
156 
157     /**
158      * {@inheritDoc}
159      *
160      * <br>
161      * The logic is the following:
162      * <table><caption>Can-generate-report Matrix</caption>
163      *   <tbody>
164      *     <tr>
165      *       <th> isAggregator </th>
166      *       <th> hasSourceFiles </th>
167      *       <th> isRootProject </th>
168      *       <th> Generate Report </th>
169      *     </tr>
170      *     <tr>
171      *       <td>True</td>
172      *       <td>True</td>
173      *       <td>True</td>
174      *       <td>True</td>
175      *     </tr>
176      *     <tr>
177      *       <td>True</td>
178      *       <td>True</td>
179      *       <td>False</td>
180      *       <td>False</td>
181      *     </tr>
182      *     <tr>
183      *       <td>True</td>
184      *       <td>False</td>
185      *       <td>True</td>
186      *       <td>False</td>
187      *     </tr>
188      *     <tr>
189      *       <td>True</td>
190      *       <td>False</td>
191      *       <td>False</td>
192      *       <td>False</td>
193      *     </tr>
194      *     <tr>
195      *       <td>False</td>
196      *       <td>True</td>
197      *       <td>True</td>
198      *       <td>True</td>
199      *     </tr>
200      *     <tr>
201      *       <td>False</td>
202      *       <td>True</td>
203      *       <td>False</td>
204      *       <td>True</td>
205      *     </tr>
206      *     <tr>
207      *        <td>False</td>
208      *        <td>False</td>
209      *        <td>True</td>
210      *        <td>False</td>
211      *      </tr>
212      *      <tr>
213      *        <td>False</td>
214      *        <td>False</td>
215      *        <td>False</td>
216      *        <td>False</td>
217      *      </tr>
218      *    </tbody>
219      *  </table>
220      */
221     @Override
222     public boolean canGenerateReport() {
223         boolean canGenerate = false;
224 
225         if (this.isAggregator() || !"pom".equals(this.project.getPackaging())) {
226             Collection<Path> sourcePaths;
227             Map<Path, Collection<String>> files;
228             try {
229                 sourcePaths = getSourcePaths().stream()
230                         .flatMap(e -> e.getSourcePaths().stream())
231                         .collect(Collectors.toList());
232                 files = getFiles(sourcePaths);
233             } catch (MavenReportException e) {
234                 getLog().error(e.getMessage(), e);
235                 return false;
236             }
237 
238             canGenerate = canGenerateReport(files);
239         }
240         if (getLog().isDebugEnabled()) {
241             getLog().debug(" canGenerateReport = " + canGenerate + " for project " + this.project);
242         }
243         return canGenerate;
244     }
245 
246     /** {@inheritDoc} */
247     @Override
248     public String getCategoryName() {
249         return CATEGORY_PROJECT_REPORTS;
250     }
251 
252     /** {@inheritDoc} */
253     @Override
254     public File getReportOutputDirectory() {
255         if (reportOutputDirectory == null) {
256             return outputDirectory;
257         }
258 
259         return reportOutputDirectory;
260     }
261 
262     /**
263      * Method to set the directory where the generated reports will be put
264      *
265      * @param reportOutputDirectory the directory file to be set
266      */
267     @Override
268     public void setReportOutputDirectory(File reportOutputDirectory) {
269         updateReportOutputDirectory(reportOutputDirectory, destDir);
270     }
271 
272     /**
273      * @param theDestDir The destination directory.
274      */
275     public void setDestDir(String theDestDir) {
276         this.destDir = theDestDir;
277         updateReportOutputDirectory(reportOutputDirectory, theDestDir);
278     }
279 
280     private void updateReportOutputDirectory(File reportOutputDirectory, String destDir) {
281         if (reportOutputDirectory != null
282                 && destDir != null
283                 && !reportOutputDirectory.getAbsolutePath().endsWith(destDir)) {
284             this.reportOutputDirectory = new File(reportOutputDirectory, destDir);
285         } else {
286             this.reportOutputDirectory = reportOutputDirectory;
287         }
288     }
289 
290     /** {@inheritDoc} */
291     @Override
292     public void doExecute() throws MojoExecutionException, MojoFailureException {
293         if (skip) {
294             getLog().info("Skipping javadoc generation");
295             return;
296         }
297 
298         File outputDirectory = new File(getOutputDirectory());
299 
300         String filename = getOutputName() + ".html";
301 
302         Locale locale = Locale.getDefault();
303 
304         try {
305             // TODO Replace null with real value
306             RenderingContext docRenderingContext = new RenderingContext(outputDirectory, filename, null);
307 
308             SiteRendererSink sink = new SiteRendererSink(docRenderingContext);
309 
310             generate(sink, null, locale);
311 
312         } catch (MavenReportException | RuntimeException e) {
313             failOnError("An error has occurred in " + getName(Locale.ENGLISH) + " report generation", e);
314         }
315     }
316 
317     /**
318      * Gets the resource bundle for the specified locale.
319      *
320      * @param locale The locale of the currently generated report.
321      * @return The resource bundle for the requested locale.
322      */
323     private ResourceBundle getBundle(Locale locale) {
324         return ResourceBundle.getBundle("javadoc-report", locale, getClass().getClassLoader());
325     }
326 }