View Javadoc

1   /*
2    * Copyright 2007-2013 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.exceptions;
17  
18  import java.io.File;
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.Locale;
22  import java.util.ResourceBundle;
23  
24  import org.apache.commons.lang.LocaleUtils;
25  import org.apache.commons.lang.StringUtils;
26  import org.apache.maven.artifact.DependencyResolutionRequiredException;
27  import org.apache.maven.plugin.AbstractMojo;
28  import org.apache.maven.plugin.MojoExecutionException;
29  import org.apache.maven.plugin.MojoFailureException;
30  import org.apache.maven.plugin.logging.Log;
31  import org.apache.maven.project.MavenProject;
32  import org.apache.maven.reporting.MavenReportException;
33  
34  import de.smartics.exceptions.report.ReportBuilder;
35  import de.smartics.exceptions.report.ReportConfiguration;
36  import de.smartics.exceptions.report.data.InMemoryExceptionCodesReport;
37  import de.smartics.exceptions.report.data.ProjectConfiguration;
38  import de.smartics.exceptions.report.data.ReportProblem;
39  import de.smartics.exceptions.report.generator.ReportGenerator;
40  import de.smartics.exceptions.report.utils.StringFunction;
41  import de.smartics.maven.exceptions.conf.ConfigUtils;
42  import de.smartics.maven.exceptions.conf.DefaultProjectConfiguration;
43  import de.smartics.maven.exceptions.runtime.ProjectClassLoader;
44  import de.smartics.maven.io.MojoIoUtils;
45  import de.smartics.maven.util.LoggingUtils;
46  import de.smartics.maven.util.PathUtils;
47  
48  /**
49   * Base implementation to generate SDoc documentation files.
50   */
51  public abstract class AbstractSdocCodeMojo extends AbstractMojo // NOPMD
52  {
53    // ********************************* Fields *********************************
54  
55    // --- constants ------------------------------------------------------------
56  
57    // --- members --------------------------------------------------------------
58  
59    // ... Mojo infrastructure ..................................................
60  
61    /**
62     * The Maven project.
63     *
64     * @parameter expression="${project}"
65     * @required
66     * @readonly
67     * @since 1.0
68     */
69    protected MavenProject project;
70  
71    /**
72     * The plugin's dependencies to build the classpath for tools to be called.
73     *
74     * @parameter expression="${plugin.artifacts}"
75     * @required
76     * @readonly
77     */
78    @SuppressWarnings("rawtypes")
79    // NOPMD
80    protected ArrayList pluginArtifacts; // NOPMD
81  
82    /**
83     * A simple flag to skip the generation of the XML files. If set on the
84     * command line use <code>-Dsdoccode.skip</code>.
85     *
86     * @parameter expression="${sdoccode.skip}" default-value="false"
87     * @since 1.0
88     */
89    protected boolean skip;
90  
91    /**
92     * Specifies the log level used for this plugin.
93     * <p>
94     * Allowed values are <code>SEVERE</code>, <code>WARNING</code>,
95     * <code>INFO</code> and <code>FINEST</code>.
96     * </p>
97     *
98     * @parameter expression="${exceptioncodes.logLevel}"
99     * @since 1.0
100    */
101   private String logLevel;
102 
103   /**
104    * The locale to use regardless of the report. This should be set to the
105    * locale the Javadoc comment is written in. If not set, the Maven provided
106    * locale is used.
107    *
108    * @parameter expression="${exceptioncodes.locale}"
109    * @since 1.0
110    */
111   protected String locale;
112 
113   // ... core information .....................................................
114 
115   // ... sources
116 
117   /**
118    * Specifies additional root directories of source files to consider. Please
119    * note that source file archives found at the same location as the class file
120    * archives are automatically visible. The source archive is required to have
121    * the suffix <code>-sources</code> in front of the extension and only jar-
122    * and zip-archives are supported.
123    *
124    * @parameter expression="${exceptioncodes.additionalSources}"
125    * @since 1.0
126    */
127   private String additionalSources;
128 
129   /**
130    * Specifies the encoding the sources will be read.
131    *
132    * @parameter expression="${project.build.sourceEncoding}"
133    *            default-value="UTF-8"
134    * @since 1.0
135    */
136   private String sourceEncoding;
137 
138   /**
139    * Specifies the Java version of the sources.
140    *
141    * @parameter expression="${exceptioncodes.build.sourceVersion}"
142    *            default-value="1.5"
143    * @since 1.0
144    */
145   private String sourceVersion;
146 
147   /**
148    * Specifies the names filter of the source and class files to be included in
149    * the reports. The path starts at the source path root, not at the project
150    * root.
151    *
152    * @parameter expression="${exceptioncodes.includes}"
153    * @since 1.0
154    */
155   @SuppressWarnings("rawtypes")
156   private ArrayList includes; // NOPMD
157 
158   /**
159    * Specifies the names filter of the source and class files to be excluded
160    * from the reports. The path starts at the source path root, not at the
161    * project root.
162    *
163    * @parameter expression="${exceptioncodes.excludes}"
164    * @since 1.0
165    */
166   @SuppressWarnings("rawtypes")
167   private ArrayList excludes; // NOPMD
168 
169   // ... sources
170 
171   /**
172    * Specifies the root directory to write the report files to.
173    *
174    * @parameter expression="${project.build.directory}/sdoc-export"
175    * @since 1.0
176    */
177   private String outputDirectory;
178 
179   /**
180    * The file to write to.
181    *
182    * @since 1.0
183    */
184   protected File output;
185 
186   /**
187    * Specifies the encoding the reports will be written.
188    *
189    * @parameter expression="${project.reporting.outputEncoding}"
190    *            default-value="UTF-8"
191    * @since 1.0
192    */
193   private String reportEncoding;
194 
195   /**
196    * Specifies the name of the report generator instance. This class has to
197    * implement <code>de.smartics.exceptions.report.ReportGenerator</code>.<br/>
198    * Currently one implementation is provided, which is the default.
199    *
200    * @parameter expression="${exceptioncodes.report.generator}" default-value=
201    *            "de.smartics.maven.exceptions.sdoc.SdocCodeReportGenerator"
202    * @since 1.0
203    */
204   private String reportGenerator;
205 
206   /**
207    * Specifies the mapper class that allows finding the the bundle associated
208    * with a given code. The referenced class requires to implement
209    * <code>de.smartics.messages.core.DefaultLocationBundleMapper</code>.
210    *
211    * @parameter default-value=
212    *            "de.smartics.messages.core.DefaultLocationBundleMapper"
213    * @since 1.0
214    */
215   private String codeBundleMapper;
216 
217   // ****************************** Initializer *******************************
218 
219   // ****************************** Constructors ******************************
220 
221   // ****************************** Inner Classes *****************************
222 
223   // ********************************* Methods ********************************
224 
225   // --- init -----------------------------------------------------------------
226 
227   // --- get&set --------------------------------------------------------------
228 
229   /**
230    * Returns the maven project.
231    *
232    * @return the maven project.
233    */
234   public MavenProject getProject()
235   {
236     return project;
237   }
238 
239   /**
240    * Sets the maven project.
241    *
242    * @param project the maven project.
243    */
244   public void setProject(final MavenProject project)
245   {
246     this.project = project;
247   }
248 
249   // --- business -------------------------------------------------------------
250 
251   /**
252    * {@inheritDoc}
253    */
254   public void execute() throws MojoExecutionException, MojoFailureException
255   {
256     if (skip)
257     {
258       getLog().info("Skipping sdoc exception code collection since skip=true.");
259       return;
260     }
261 
262     if (canBuild())
263     {
264       init();
265       final Locale locale = determineLocale();
266 
267       try
268       {
269         final ProjectConfiguration<File> projectConfig =
270             createProjectConfiguration(locale, output);
271         final ReportConfiguration reportConfig =
272             createReportConfiguration(projectConfig);
273 
274         final ReportBuilder builder = ReportBuilder.create(reportConfig);
275         final InMemoryExceptionCodesReport report =
276             new InMemoryExceptionCodesReport();
277         builder.reportTo(report);
278 
279         if (report.hasProblems())
280         {
281           final String message = toString(report.getProblems());
282           getLog().error(message);
283           throw new MavenReportException(
284               "Problems encountered while generating report.\n"
285                   + " Please refer to error log for details.");
286         }
287 
288         final ReportGenerator<File> generator =
289             projectConfig.getReporterInstance();
290         generator.setOutput(output);
291         generator.writeReport(projectConfig, report);
292 
293         handleGeneratedDocuments();
294       }
295       catch (final Exception e)
296       {
297         throw new MojoExecutionException(
298             "Cannot generate code export for projectdoc.", e);
299       }
300     }
301     else
302     {
303       getLog().debug(
304           "Skipping sdoccode since no classes are provided by this project.");
305     }
306   }
307 
308   private static String toString(final List<ReportProblem> problems)
309   {
310     final StringBuilder buffer = new StringBuilder(512);
311 
312     for (final ReportProblem problem : problems)
313     {
314       buffer.append(problem).append('\n');
315     }
316 
317     return buffer.toString();
318   }
319 
320   /**
321    * Handles the generated documents.
322    *
323    * @throws MojoExecutionException if the execution encounters a problem.
324    * @throws MojoFailureException if the mojo fails.
325    */
326   protected abstract void handleGeneratedDocuments()
327     throws MojoExecutionException, MojoFailureException;
328 
329   @SuppressWarnings("unchecked")
330   // NOPMD
331   private void init() throws MojoExecutionException
332   {
333     LoggingUtils.configureLogger(getLog(), logLevel);
334     if (includes.isEmpty())
335     {
336       includes.add("**");
337     }
338 
339     if (StringUtils.isBlank(codeBundleMapper))
340     {
341       throw new IllegalArgumentException(
342           "No code bundle mapper class provided."
343               + " Please specify one by the mojo parameter 'codeBundleMapper'.");
344     }
345 
346     this.output = MojoIoUtils.provideMojoDirectory(outputDirectory);
347   }
348 
349   /**
350    * Determines the locale to use. The plugin allows the user to override the
351    * locale provided by Maven.
352    *
353    * @return the locale to use for this report.
354    */
355   private Locale determineLocale()
356   {
357     return StringUtils.isNotBlank(this.locale) ? LocaleUtils
358         .toLocale(this.locale) : Locale.getDefault();
359   }
360 
361   @SuppressWarnings("unchecked")
362   private boolean canBuild()
363   {
364     final String packaging = project.getPackaging();
365     return !("pom".equals(packaging) || !PathUtils.exists(project
366         .getCompileSourceRoots()));
367   }
368 
369   @SuppressWarnings("unchecked")
370   private ProjectConfiguration<File> createProjectConfiguration(
371       final Locale locale, final File outputDir)
372     throws DependencyResolutionRequiredException
373   {
374     final List<String> classRootDirectoryNames =
375         project.getCompileClasspathElements();
376 
377     final List<String> sourceRootDirectoryNames = new ArrayList<String>();
378     sourceRootDirectoryNames.addAll(StringFunction.split(
379         this.additionalSources, ","));
380     sourceRootDirectoryNames.addAll(project.getCompileSourceRoots());
381     sourceRootDirectoryNames.addAll(ConfigUtils
382         .discoverSourceJars(classRootDirectoryNames));
383     final List<String> toolClassPath = PathUtils.toClassPath(pluginArtifacts);
384 
385     final Log log = getLog();
386     if (log.isDebugEnabled())
387     {
388       log.debug("Tool class path: " + toolClassPath);
389       log.debug("Class roots    : " + classRootDirectoryNames);
390       log.debug("Source roots   : " + sourceRootDirectoryNames);
391     }
392 
393     final DefaultProjectConfiguration.Builder<File> builder =
394         new DefaultProjectConfiguration.Builder<File>(project.getName());
395 
396     builder.setIncludes(includes);
397     builder.setExcludes(excludes);
398     builder.setSourceEncoding(this.sourceEncoding);
399     builder.setSourceVersion(this.sourceVersion);
400     builder.setToolClassPath(toolClassPath);
401     builder.setClassRootDirectoryNames(classRootDirectoryNames);
402     builder.setSourceRootDirectoryNames(sourceRootDirectoryNames);
403     builder.setJavadocDir(null);
404 
405     builder.setReportEncoding(this.reportEncoding);
406     builder.setReporter(this.reportGenerator);
407     builder.setReport(outputDir.getAbsolutePath());
408 
409     final ResourceBundle bundle = getBundle(locale);
410     builder.setBundle(bundle);
411     builder.setBundleMapperClassName(this.codeBundleMapper);
412 
413     final ProjectConfiguration<File> config = builder.build();
414     return config;
415   }
416 
417   private ReportConfiguration createReportConfiguration(
418       final ProjectConfiguration<File> projectConfig)
419   {
420     final ReportConfiguration reportConfig = new ReportConfiguration();
421     reportConfig.setEncoding(this.sourceEncoding);
422     final ProjectClassLoader classLoader =
423         new ProjectClassLoader(Thread.currentThread().getContextClassLoader(),
424             projectConfig.getClassRootDirectoryNames());
425     reportConfig.setProjectClassLoader(classLoader);
426     for (final String name : projectConfig.getSourceRootDirectoryNames())
427     {
428       final File file = new File(name);
429       reportConfig.addSourceTree(file);
430     }
431 
432     reportConfig.addIncludes(projectConfig.getIncludes());
433     reportConfig.addExcludes(projectConfig.getExcludes());
434 
435     return reportConfig;
436   }
437 
438   private ResourceBundle getBundle(final Locale locale)
439   {
440     return ResourceBundle.getBundle(getBundleName(), locale, getClass()
441         .getClassLoader()); // NOPMD
442   }
443 
444   private String getBundleName()
445   {
446     return "de/smartics/maven/exceptions/sdoc/SdocCodeBundle";
447   }
448 
449   // --- object basics --------------------------------------------------------
450 
451 }