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.maven.artifact.DependencyResolutionRequiredException;
25  import org.apache.maven.doxia.sink.Sink;
26  import org.apache.maven.plugin.logging.Log;
27  import org.apache.maven.project.MavenProject;
28  import org.apache.maven.reporting.MavenReportException;
29  
30  import de.smartics.exceptions.report.ReportBuilder;
31  import de.smartics.exceptions.report.ReportConfiguration;
32  import de.smartics.exceptions.report.data.InMemoryExceptionCodesReport;
33  import de.smartics.exceptions.report.data.ProjectConfiguration;
34  import de.smartics.exceptions.report.data.ReportProblem;
35  import de.smartics.exceptions.report.generator.ReportGenerator;
36  import de.smartics.exceptions.report.utils.StringFunction;
37  import de.smartics.maven.exceptions.conf.ConfigUtils;
38  import de.smartics.maven.exceptions.conf.DefaultProjectConfiguration;
39  import de.smartics.maven.exceptions.runtime.ProjectClassLoader;
40  import de.smartics.maven.util.PathUtils;
41  
42  /**
43   * Generates exception code documentation.
44   *
45   * @goal report
46   * @phase site
47   * @requiresProject
48   * @requiresDependencyResolution
49   * @description Generates documentation for the exception codes used in the
50   *              project.
51   */
52  public class ExceptionCodeReport extends AbstractElementReport
53  {
54    // ********************************* Fields *********************************
55  
56    // --- constants ------------------------------------------------------------
57  
58    // --- members --------------------------------------------------------------
59  
60    // ... plugin infrastructure ................................................
61  
62    // ... references to artifacts of other plugins .............................
63  
64    // ... own configuration parameters .........................................
65  
66    // ... basics
67  
68    /**
69     * Specifies the name of the file to save the properties report (without
70     * extension).
71     *
72     * @parameter expression="${properties.report.fileName}" default-value=
73     *            "exception-codes-report"
74     */
75    private String outputName;
76  
77    // ... sources
78  
79    /**
80     * Specifies additional root directories of source files to consider. Please
81     * note that source file archives found at the same location as the class file
82     * archives are automatically visible. The source archive is required to have
83     * the suffix <code>-sources</code> in front of the extension and only jar-
84     * and zip-archives are supported.
85     *
86     * @parameter expression="${exceptioncodes.additionalSources}"
87     */
88    private String additionalSources;
89  
90    /**
91     * Specifies the encoding the sources will be read.
92     *
93     * @parameter expression="${exceptioncodes.build.sourceEncoding}"
94     *            default-value="UTF-8"
95     */
96    private String sourceEncoding;
97  
98    /**
99     * Specifies the Java version of the sources.
100    *
101    * @parameter expression="${exceptioncodes.build.sourceVersion}"
102    *            default-value="1.5"
103    */
104   private String sourceVersion;
105 
106   /**
107    * Specifies the names filter of the source and class files to be included in
108    * the reports. The path starts at the source path root, not at the project
109    * root.
110    *
111    * @parameter expression="${exceptioncodes.includes}"
112    * @since 1.0
113    */
114   @SuppressWarnings("rawtypes")
115   private ArrayList includes; // NOPMD
116 
117   /**
118    * Specifies the names filter of the source and class files to be excluded
119    * from the reports. The path starts at the source path root, not at the
120    * project root.
121    *
122    * @parameter expression="${exceptioncodes.excludes}"
123    * @since 1.0
124    */
125   @SuppressWarnings("rawtypes")
126   private ArrayList excludes; // NOPMD
127 
128   // ... report
129 
130   /**
131    * Specifies the name of the report generator instance. This class has to
132    * implement <code>de.smartics.exceptions.report.ReportGenerator</code>. <br/>
133    * Currently three implementations are provided:
134    * <code>de.smartics.maven.exceptions.report.PackageSortedSinkReportGenerator</code>
135    * ,
136    * <code>de.smartics.maven.exceptions.report.CodeSortedSinkReportGenerator</code>
137    * , and
138    * <code>de.smartics.maven.exceptions.report.ComponentCodeSortedSinkReportGenerator</code>
139    * . <br/>
140    *
141    * @parameter expression="${exceptioncodes.report.generator}" default-value=
142    *            "de.smartics.maven.exceptions.report.PackageSortedSinkReportGenerator"
143    */
144   private String reportGenerator;
145 
146   /**
147    * Specifies the encoding the report will be written.
148    *
149    * @parameter expression="${exceptioncodes.report.encoding}"
150    *            default-value="UTF-8"
151    */
152   private String reportEncoding;
153 
154   /**
155    * A simple flag to skip the generation of the report. If set on the command
156    * line use <code>-Dexceptioncodes.skip</code>.
157    *
158    * @parameter expression="${exceptioncodes.skip}" default-value="false"
159    * @since 1.0
160    */
161   private boolean skip;
162 
163   /**
164    * Flag to add source pathes of modules of a multi module build. This allows
165    * to reference codes of all dependent modules.
166    *
167    * @parameter expression="${exceptioncodes.addModuleSrcFolders}"
168    *            default-value="true"
169    * @since 1.0
170    */
171   private boolean addModuleSrcFolders;
172 
173   // ****************************** Initializer *******************************
174 
175   // ****************************** Constructors ******************************
176 
177   // ****************************** Inner Classes *****************************
178 
179   // ********************************* Methods ********************************
180 
181   // --- init -----------------------------------------------------------------
182 
183   // --- get&set --------------------------------------------------------------
184 
185   // ... plugin metadata ......................................................
186 
187   // ... plugin infrastructure ................................................
188 
189   /**
190    * {@inheritDoc}
191    *
192    * @see org.apache.maven.reporting.AbstractMavenReport#getOutputName()
193    */
194   public String getOutputName()
195   {
196     return outputName;
197   }
198 
199   // --- business -------------------------------------------------------------
200 
201   @Override
202   public void executeReport(final Locale locale) throws MavenReportException
203   {
204     if (skip)
205     {
206       final Log log = getLog();
207       if (log.isInfoEnabled())
208       {
209         log.info("Report '" + getName(locale) + "' skipped.");
210       }
211       return;
212     }
213 
214     try
215     {
216       final ProjectConfiguration<Sink> projectConfig =
217           createProjectConfiguration(locale);
218       final ReportConfiguration reportConfig =
219           createReportConfiguration(projectConfig);
220 
221       final ReportBuilder builder = ReportBuilder.create(reportConfig);
222       final InMemoryExceptionCodesReport report =
223           new InMemoryExceptionCodesReport();
224       builder.reportTo(report);
225 
226       if (report.hasProblems())
227       {
228         final String message = toString(report.getProblems());
229         getLog().error(message);
230         throw new MavenReportException(
231             "Problems encountered while generating report.\n"
232                 + " Please refer to error log for details.");
233       }
234 
235       final ReportGenerator<Sink> generator =
236           projectConfig.getReporterInstance();
237       generator.writeReport(projectConfig, report);
238     }
239     catch (final Exception e)
240     {
241       e.printStackTrace();
242       throw new MavenReportException(
243           "Cannot generate " + getName(locale) + ".", e);
244     }
245   }
246 
247   private static String toString(final List<ReportProblem> problems)
248   {
249     final StringBuilder buffer = new StringBuilder(512);
250 
251     for (final ReportProblem problem : problems)
252     {
253       buffer.append(problem).append('\n');
254     }
255 
256     return buffer.toString();
257   }
258 
259   @SuppressWarnings("unchecked")
260   private ProjectConfiguration<Sink> createProjectConfiguration(
261       final Locale locale) throws DependencyResolutionRequiredException
262   {
263     final List<String> classRootDirectoryNames =
264         project.getCompileClasspathElements();
265 
266     final List<String> sourceRootDirectoryNames = new ArrayList<String>();
267     sourceRootDirectoryNames.addAll(StringFunction.split(
268         this.additionalSources, ","));
269     addModuleSources(sourceRootDirectoryNames);
270     sourceRootDirectoryNames.addAll(project.getCompileSourceRoots());
271     sourceRootDirectoryNames.addAll(ConfigUtils
272         .discoverSourceJars(classRootDirectoryNames));
273     final List<String> toolClassPath = PathUtils.toClassPath(pluginArtifacts);
274 
275     final Log log = getLog();
276     if (log.isDebugEnabled())
277     {
278       log.debug("Tool class path: " + toolClassPath);
279       log.debug("Class roots    : " + classRootDirectoryNames);
280       log.debug("Source roots   : " + sourceRootDirectoryNames);
281     }
282 
283     final DefaultProjectConfiguration.Builder<Sink> builder =
284         new DefaultProjectConfiguration.Builder<Sink>(project.getName());
285 
286     builder.setIncludes(includes);
287     builder.setExcludes(excludes);
288     builder.setSourceEncoding(this.sourceEncoding);
289     builder.setSourceVersion(this.sourceVersion);
290     builder.setToolClassPath(toolClassPath);
291     builder.setClassRootDirectoryNames(classRootDirectoryNames);
292     builder.setSourceRootDirectoryNames(sourceRootDirectoryNames);
293     builder.setJavadocDir(javadocDir);
294 
295     builder.setReportEncoding(this.reportEncoding);
296     builder.setReporter(this.reportGenerator);
297     builder.setReport(new File(this.outputDirectory.getAbsolutePath(),
298         outputName).getPath());
299 
300     final ResourceBundle bundle = getBundle(locale);
301     builder.setBundle(bundle);
302 
303     final ProjectConfiguration<Sink> config = builder.build();
304 
305     final Sink sink = getSink();
306     final ReportGenerator<Sink> generator = config.getReporterInstance();
307     generator.setOutput(sink);
308 
309     return config;
310   }
311 
312   private void addModuleSources(final List<String> sourceRootDirectoryNames)
313   {
314     if (!addModuleSrcFolders)
315     {
316       return;
317     }
318 
319     final List<MavenProject> projects = session.getProjects();
320     for (final MavenProject current : projects)
321     {
322       if (project.getDependencyArtifacts().contains(current.getArtifact()))
323       {
324         final String sourceDir = current.getBuild().getSourceDirectory();
325         if (new File(sourceDir).exists())
326         {
327           sourceRootDirectoryNames.add(sourceDir);
328         }
329       }
330     }
331   }
332 
333   private ReportConfiguration createReportConfiguration(
334       final ProjectConfiguration<Sink> projectConfig)
335   {
336     final ReportConfiguration reportConfig = new ReportConfiguration();
337     reportConfig.setEncoding(this.sourceEncoding);
338     final ProjectClassLoader classLoader =
339         new ProjectClassLoader(Thread.currentThread().getContextClassLoader(),
340             projectConfig.getClassRootDirectoryNames());
341     reportConfig.setProjectClassLoader(classLoader);
342     for (final String name : projectConfig.getSourceRootDirectoryNames())
343     {
344       final File file = new File(name);
345       reportConfig.addSourceTree(file);
346     }
347 
348     reportConfig.addIncludes(projectConfig.getIncludes());
349     reportConfig.addExcludes(projectConfig.getExcludes());
350 
351     return reportConfig;
352   }
353 
354   @Override
355   protected String getBundleName()
356   {
357     return "de/smartics/exceptions/report/ExceptionCodeReportBundle";
358   }
359 
360   // --- object basics --------------------------------------------------------
361 
362 }