View Javadoc

1   /*
2    * Copyright 2009-2012 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.smartics.maven.apidoc;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.LinkedHashMap;
22  import java.util.List;
23  import java.util.Locale;
24  import java.util.Map;
25  
26  import org.apache.maven.artifact.Artifact;
27  import org.apache.maven.artifact.DependencyResolutionRequiredException;
28  import org.apache.maven.plugin.logging.Log;
29  import org.apache.maven.reporting.MavenReportException;
30  import org.codehaus.plexus.util.StringUtils;
31  
32  import de.smartics.analysis.javadoc.JavadocArgumentsUtils;
33  import de.smartics.analysis.javadoc.JavadocUtils;
34  import de.smartics.analysis.javadoc.conf.DefaultJavadocProjectConfiguration;
35  import de.smartics.analysis.javadoc.conf.JavadocProjectConfiguration;
36  import de.smartics.analysis.javadoc.log.JavadocMessageLogger;
37  import de.smartics.analysis.javadoc.util.StringFunction;
38  import de.smartics.maven.util.PathUtils;
39  
40  /**
41   * API doc report.
42   *
43   * @goal apidoc-report
44   * @phase site
45   * @description Generates a report on Javadoc comments.
46   * @requiresProject
47   * @requiresDependencyResolution
48   * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a>
49   * @version $Revision:591 $
50   */
51  public class ApiDocReportMojo extends AbstractReportMojo
52  {
53    // ********************************* Fields *********************************
54  
55    // --- constants ------------------------------------------------------------
56  
57    // --- members --------------------------------------------------------------
58  
59    /**
60     * The plugin's dependencies to build the classpath for tools to be called.
61     *
62     * @parameter expression="${plugin.artifacts}"
63     * @required
64     * @readonly
65     * @since 1.0
66     */
67    protected List<Artifact> pluginArtifacts;
68  
69    /**
70     * Specifies additional root directories of source files to consider. Please
71     * note that source file archives found at the same location as the class file
72     * archives are automatically visible. The source archive is required to have
73     * the suffix <code>-sources</code> in front of the extension and only jar-
74     * and zip-archives are supported.
75     *
76     * @parameter expression="${apidoc.additionalSources}"
77     * @since 1.0
78     */
79    private String additionalSources;
80  
81    /**
82     * Specifies the encoding the sources will be read.
83     *
84     * @parameter expression="${apidoc.build.sourceEncoding}"
85     *            default-value="UTF-8"
86     * @since 1.0
87     */
88    private String sourceEncoding;
89  
90    /**
91     * Specifies the encoding the sources will be read.
92     *
93     * @parameter expression="${apidoc.build.sourceVersion}" default-value="1.5"
94     * @since 1.0
95     */
96    private String sourceVersion;
97  
98    /**
99     * Specifies the names filter of the source and class files to be included in
100    * the report.
101    *
102    * @parameter expression="${apidoc.includes}" default-value="**"
103    * @since 1.0
104    */
105   private String includes;
106 
107   /**
108    * Specifies the names filter of the source and class files to be excluded
109    * from the report.
110    *
111    * @parameter expression="${apidoc.excludes}"
112    * @since 1.0
113    */
114   private String excludes;
115 
116   /**
117    * The location the XRef report is rendered to, relative to the site
118    * directory.
119    *
120    * @parameter default-value="xref"
121    * @since 1.0
122    */
123   private String xrefLocation;
124 
125   /**
126    * The flag that indicates if notice messages should be rendered in the report
127    * (<code>true</code>) or if only error and warn messages are rendered (
128    * <code>false</code>).
129    *
130    * @parameter default-value="false"
131    * @since 1.0
132    */
133   private boolean noticeMessagesRendered;
134 
135   /**
136    * The doclet to use to listen to errors, warnings and issues.
137    *
138    * @parameter default-value="com.sun.tools.doclets.standard.Standard"
139    * @since 1.0
140    */
141   private String doclet;
142 
143   /**
144    * The report set to watch for additional parameters. This is useful if you
145    * want to include the additional parameters of the <a
146    * href="http://maven.apache.org/plugins/maven-javadoc-plugin/"
147    * >maven-javadoc-plugin</a> to be used in this analysis. Please note that
148    * only those <code>additionalparam</code> elements are used that provide an
149    * id that starts with the character sequence "<code>apidoc</code>".
150    *
151    * @parameter default-value="default"
152    * @since 1.0
153    */
154   private String reportSetId;
155 
156   /**
157    * Set an additional parameter(s) on the command line. This value should
158    * include quotes as necessary for parameters that include spaces.
159    *
160    * @parameter expression="${apidoc.additionalparam}"
161    * @since 1.0
162    */
163   private String additionalparam;
164 
165   /**
166    * The list of message fragments that are used to filter messages. If a
167    * message contains a fragment found in the list, it will be suppressed.
168    *
169    * @parameter
170    * @since 1.0
171    */
172   private List<String> messageFilter;
173 
174   // ****************************** Initializer *******************************
175 
176   // ****************************** Constructors ******************************
177 
178   // ****************************** Inner Classes *****************************
179 
180   // ********************************* Methods ********************************
181 
182   // --- init -----------------------------------------------------------------
183 
184   // --- get&set --------------------------------------------------------------
185 
186   /**
187    * {@inheritDoc}
188    *
189    * @see org.apache.maven.reporting.MavenReport#getName(java.util.Locale)
190    */
191   public String getName(final Locale locale)
192   {
193     return getBundle(locale).getString("report.name");
194   }
195 
196   /**
197    * {@inheritDoc}
198    *
199    * @see org.apache.maven.reporting.MavenReport#getDescription(java.util.Locale)
200    */
201   public String getDescription(final Locale locale)
202   {
203     return getBundle(locale).getString("report.description");
204   }
205 
206   /**
207    * {@inheritDoc}
208    *
209    * @see org.apache.maven.reporting.MavenReport#getOutputName()
210    */
211   public String getOutputName()
212   {
213     return "apidoc-report";
214   }
215 
216   // --- business -------------------------------------------------------------
217 
218   /**
219    * Creates the project configuration to control the report generation.
220    *
221    * @return the configured configuration.
222    * @throws DependencyResolutionRequiredException if the dependencies cannot be
223    *           resolved.
224    */
225   @SuppressWarnings("unchecked")
226   private JavadocProjectConfiguration createProjectConfiguration()
227       throws DependencyResolutionRequiredException
228   {
229     final List<String> classRootDirectoryNames =
230         project.getCompileClasspathElements();
231 
232     final List<String> sourceRootDirectoryNames = new ArrayList<String>();
233     sourceRootDirectoryNames.addAll(StringFunction.split(
234         this.additionalSources, ","));
235     sourceRootDirectoryNames.addAll(project.getCompileSourceRoots());
236     final List<String> toolClassPath = PathUtils.toClassPath(pluginArtifacts);
237 
238     final Log log = getLog();
239     if (log.isDebugEnabled())
240     {
241       log.debug("Tool class path: " + toolClassPath);
242       log.debug("Class roots    : " + classRootDirectoryNames);
243       log.debug("Source roots   : " + sourceRootDirectoryNames);
244     }
245 
246     final List<String> includesList = StringFunction.split(this.includes, ",");
247     final List<String> excludesList = StringFunction.split(this.excludes, ",");
248 
249     final DefaultJavadocProjectConfiguration.Builder builder =
250         new DefaultJavadocProjectConfiguration.Builder(project.getName());
251 
252     builder.setIncludes(includesList);
253     builder.setExcludes(excludesList);
254     builder.setSourceEncoding(this.sourceEncoding);
255     builder.setSourceVersion(this.sourceVersion);
256     builder.setToolClassPath(toolClassPath);
257     builder.setClassRootDirectoryNames(classRootDirectoryNames);
258     builder.setSourceRootDirectoryNames(sourceRootDirectoryNames);
259 
260     final JavadocProjectConfiguration config = builder.build();
261     return config;
262   }
263 
264   /**
265    * {@inheritDoc}
266    *
267    * @see org.apache.maven.reporting.AbstractMavenReport#executeReport(java.util.Locale)
268    */
269   @Override
270   protected void executeReport(final Locale locale) throws MavenReportException
271   {
272     super.executeReport(locale);
273 
274     try
275     {
276       final String toolId = "apidoc";
277       final JavadocMessageLogger logger = new JavadocMessageLogger();
278 
279       final JavadocProjectConfiguration config = createProjectConfiguration();
280       final Map<String, String> arguments = configureArguments(config);
281       final Log log = getLog();
282       if (!checkConfiguration(arguments))
283       {
284         if (log.isInfoEnabled())
285         {
286           log.info("Report generation skipped. No packages to report.");
287         }
288         return;
289       }
290 
291       if (log.isDebugEnabled())
292       {
293         log.debug("Arguments passed to Javadoc tool: " + arguments);
294       }
295 
296       final int returnCode =
297           JavadocUtils.executeJavadocParser(toolId, doclet, arguments, logger);
298       if (returnCode != 0)
299       {
300         handleJavadocToolErrorCode(logger);
301       }
302 
303       renderReport(locale, config, logger);
304     }
305     catch (final Exception e)
306     {
307       final String message = "Cannot generate " + getName(locale) + ".";
308       if (getLog().isWarnEnabled())
309       {
310         getLog().warn(message, e);
311       }
312       throw new MavenReportException(message, e);
313     }
314   }
315 
316   private void handleJavadocToolErrorCode(final JavadocMessageLogger logger)
317       throws IllegalStateException
318   {
319     final String loggerMessage = logger.toString();
320     if (loggerMessage
321         .contains("No public or protected classes found to document"))
322     {
323       return;
324     }
325     else
326     {
327       throw new IllegalStateException("Cannot parse Java files: "
328           + loggerMessage);
329     }
330   }
331 
332   /**
333    * Creates the arguments to be passed to the Javadoc tool.
334    *
335    * @param config the configuration that creates the arguments.
336    * @return the arguments to be passed to the Javadoc tool.
337    * @throws IOException if the target directory does not exist and cannot be
338    *           created.
339    */
340   private Map<String, String> configureArguments(
341       final JavadocProjectConfiguration config) throws IOException
342   {
343     final Map<String, String> arguments = new LinkedHashMap<String, String>();
344     if (StringUtils.isNotBlank(additionalparam))
345     {
346       arguments.put(Constants.ADDITIONAL_PARAMS, additionalparam);
347     }
348     final JavadocPluginConfigurationExtractor extractor =
349         new JavadocPluginConfigurationExtractor();
350     extractor.addJavadocPluginArguments(project, reportSetId, arguments);
351     JavadocArgumentsUtils.addJavadocArguments(config, arguments);
352 
353     setTargetDirAsArgument(arguments);
354     return arguments;
355   }
356 
357   /**
358    * Checks the arguments passed to the Javadoc tool. If the arguments provide
359    * no packages, no report is to be generated.
360    *
361    * @param arguments the arguments about to be passed to the Javadoc too.
362    * @return <code>true</code> if the arguments are valid and should be passed
363    *         to generate the report, <code>false</code> if the arguments lack
364    *         information and the report generation should be skipped without
365    *         warning.
366    */
367   private boolean checkConfiguration(final Map<String, String> arguments)
368   {
369     final String packages = arguments.get(JavadocUtils.PARAM_NAME_PACKAGENAMES);
370     return StringUtils.isNotBlank(packages);
371   }
372 
373   /**
374    * Sets the target directory of the Javadoc report to the arguments that are
375    * passed to the Javadoc tool.
376    *
377    * @param arguments the arguments that are passed to the Javadoc tool.
378    * @throws IOException if the target directory does not exist and cannot be
379    *           created.
380    */
381   private void setTargetDirAsArgument(final Map<String, String> arguments)
382       throws IOException
383   {
384     final File targetDir =
385         new File(project.getBuild().getDirectory(), "apidoc-report");
386     if (!targetDir.exists() && !targetDir.mkdirs())
387     {
388       throw new IOException("Cannot create directory " + targetDir.getPath());
389     }
390     arguments.put("-d", targetDir.getPath());
391   }
392 
393   /**
394    * Renders the report.
395    *
396    * @param locale the locale of the report to be rendered.
397    * @param projectConfig the project configuration to access project
398    *          information.
399    * @param logger the logger with the messages from the Javadoc tool.
400    * @throws DependencyResolutionRequiredException
401    */
402   private void renderReport(
403       final Locale locale,
404       final JavadocProjectConfiguration projectConfig,
405       final JavadocMessageLogger logger)
406   {
407     getLog().debug(logger.toString());
408 
409     final JavadocMessageConfig.Builder builder =
410         new JavadocMessageConfig.Builder();
411     builder.setBundle(getBundle(locale));
412 
413     builder.setSourceRoots(projectConfig.getSourceRootDirectoryNames());
414     builder.setXrefLocation(xrefLocation);
415     builder.setNoticeMessagesRendered(noticeMessagesRendered);
416     builder.setMessageFilter(messageFilter);
417     final JavadocMessageConfig messageConfig = builder.build();
418 
419     final JavadocMessageReportRenderer renderer =
420         new JavadocMessageReportRenderer(getSink(), messageConfig, logger);
421     renderer.render();
422   }
423 
424   // --- object basics --------------------------------------------------------
425 
426 }