View Javadoc
1   /*
2    * Copyright (C) 2011 the original author or authors.
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  
17  package org.codehaus.gmavenplus.mojo;
18  
19  import org.apache.maven.plugins.annotations.Parameter;
20  import org.apache.maven.shared.model.fileset.FileSet;
21  import org.apache.maven.shared.model.fileset.util.FileSetManager;
22  import org.codehaus.gmavenplus.javaparser.LanguageLevel;
23  import org.codehaus.gmavenplus.model.IncludeClasspath;
24  import org.codehaus.gmavenplus.model.Link;
25  import org.codehaus.gmavenplus.model.Scopes;
26  import org.codehaus.gmavenplus.model.internal.Version;
27  import org.codehaus.gmavenplus.util.FileUtils;
28  
29  import org.codehaus.gmavenplus.model.GroovyDocConfiguration;
30  import org.codehaus.gmavenplus.util.GroovyCompiler;
31  
32  import java.io.BufferedReader;
33  import java.io.BufferedWriter;
34  import java.io.File;
35  import java.io.IOException;
36  import java.io.InputStreamReader;
37  import java.io.OutputStreamWriter;
38  import java.lang.reflect.InvocationTargetException;
39  import java.net.MalformedURLException;
40  import java.nio.file.Files;
41  import java.util.ArrayList;
42  import java.util.List;
43  import java.util.Properties;
44  
45  
46  /**
47   * The base GroovyDoc mojo, which all GroovyDoc mojos extend.
48   *
49   * @author Keegan Witt
50   */
51  public abstract class AbstractGroovyDocMojo extends AbstractGroovySourcesMojo {
52  
53      /**
54       * Groovy 1.6.0 RC-1 version.
55       */
56      protected static final Version GROOVY_1_6_0_RC1 = new Version(1, 6, 0, "RC-1");
57  
58      /**
59       * Groovy 1.5.8 version.
60       */
61      protected static final Version GROOVY_1_5_8 = new Version(1, 5, 8);
62  
63      /**
64       * The window title.
65       */
66      @Parameter(defaultValue = "Groovy Documentation")
67      protected String windowTitle;
68  
69      /**
70       * The page title.
71       */
72      @Parameter(defaultValue = "Groovy Documentation")
73      protected String docTitle;
74  
75      /**
76       * The page footer.
77       */
78      @Parameter(defaultValue = "Groovy Documentation")
79      protected String footer;
80  
81      /**
82       * The Java language level to use for GroovyDoc generation.
83       */
84      @Parameter
85      protected LanguageLevel languageLevel;
86  
87      /**
88       * The page header.
89       */
90      @Parameter(defaultValue = "Groovy Documentation")
91      protected String header;
92  
93      /**
94       * Whether to display the author in the generated GroovyDoc.
95       */
96      @Parameter(defaultValue = "true")
97      protected boolean displayAuthor;
98  
99      /**
100      * The HTML file to be used for overview documentation.
101      */
102     @Parameter
103     protected File overviewFile;
104 
105     /**
106      * The stylesheet file (absolute path) to copy to output directory (will overwrite default stylesheet.css).
107      */
108     @Parameter
109     protected File stylesheetFile;
110 
111     /**
112      * The encoding of stylesheetFile.
113      */
114     @Parameter(defaultValue = "${project.build.sourceEncoding}")
115     protected String stylesheetEncoding;
116 
117     /**
118      * The scope to generate GroovyDoc for. Should be one of:
119      * <ul>
120      *   <li>"public"</li>
121      *   <li>"protected"</li>
122      *   <li>"package"</li>
123      *   <li>"private"</li>
124      * </ul>
125      */
126     @Parameter(defaultValue = "private")
127     protected String scope;
128 
129     /**
130      * Links to include in the generated GroovyDoc (key is link href, value is comma-separated packages to use that link).
131      *
132      * @since 1.0-beta-2
133      */
134     @Parameter
135     protected List<Link> links;
136 
137     /**
138      * Flag to allow GroovyDoc generation to be skipped.
139      *
140      * @since 1.6
141      */
142     @Parameter(property = "skipGroovydoc", defaultValue = "false")
143     protected boolean skipGroovyDoc;
144 
145     /**
146      * What classpath to include. One of
147      * <ul>
148      *   <li>PROJECT_ONLY</li>
149      *   <li>PROJECT_AND_PLUGIN</li>
150      *   <li>PLUGIN_ONLY</li>
151      * </ul>
152      * Uses the same scope as the required dependency resolution of this mojo. Use only if you know what you're doing.
153      *
154      * @since 1.8.0
155      */
156     @Parameter(defaultValue = "PROJECT_ONLY")
157     protected IncludeClasspath includeClasspath;
158 
159     /**
160      * Override the default Groovydoc default top-level templates. Uses Groovy's standard templates by default.
161      *
162      * @since 1.10.1
163      */
164     @Parameter
165     protected String[] defaultDocTemplates = null;
166 
167     /**
168      * Override the default Groovydoc package-level templates. Uses Groovy's standard templates by default.
169      *
170      * @since 1.10.1
171      */
172     @Parameter
173     protected String[] defaultPackageTemplates = null;
174 
175     /**
176      * Override the default Groovydoc class-level templates. Uses Groovy's standard templates by default.
177      *
178      * @since 1.10.1
179      */
180     @Parameter
181     protected String[] defaultClassTemplates = null;
182 
183     /**
184      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.GroovyDocTool, for use when
185      * creating custom GroovyDoc implementations.
186      *
187      * @since 1.10.1
188      */
189     @Parameter
190     protected String groovyDocToolClass = null;
191 
192     /**
193      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.OutputTool, for use when
194      * creating custom GroovyDoc implementations.
195      *
196      * @since 1.10.1
197      */
198     @Parameter
199     protected String outputToolClass = null;
200 
201     /**
202      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.FileOutputTool, for use
203      * when creating custom GroovyDoc implementations.
204      *
205      * @since 1.10.1
206      */
207     @Parameter
208     protected String fileOutputToolClass = null;
209 
210     /**
211      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.ResourceManager, for use
212      * when creating custom GroovyDoc implementations.
213      *
214      * @since 1.10.1
215      */
216     @Parameter
217     protected String resourceManagerClass = null;
218 
219     /**
220      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.ClasspathResourceManager,
221      * for use when creating custom GroovyDoc implementations.
222      *
223      * @since 1.10.1
224      */
225     @Parameter
226     protected String classpathResourceManagerClass = null;
227 
228     /**
229      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.LinkArgument (or
230      * org.codehaus.groovy.ant.Groovydoc$LinkArgument for Groovy older than 1.6-RC-2), for use when creating custom
231      * GroovyDoc implementations.
232      *
233      * @since 1.10.1
234      */
235     @Parameter
236     protected String linkArgumentClass = null;
237 
238     /**
239      * Enable attaching GroovyDoc annotation. Requires Groovy 3.0.0 alpha-4 or newer.
240      *
241      * @since 1.11.0
242      */
243     @Parameter(defaultValue = "false")
244     protected boolean attachGroovyDocAnnotation;
245 
246     /**
247      * The Maven ToolchainManager.
248      */
249     @javax.inject.Inject
250     protected org.apache.maven.toolchain.ToolchainManager toolchainManager;
251 
252     /**
253      * The Maven Session.
254      */
255     @Parameter(defaultValue = "${session}", readonly = true, required = true)
256     protected org.apache.maven.execution.MavenSession session;
257 
258     /**
259      * Whether to execute in a forked process.
260      *
261      * @since 4.3.0
262      */
263     @Parameter(property = "fork", defaultValue = "false")
264     protected boolean fork;
265 
266     /**
267      * Generates the GroovyDoc for the specified sources.
268      *
269      * @param sourceDirectories The source directories to generate GroovyDoc for
270      * @param classpath         The classpath to use for compilation
271      * @param outputDirectory   The directory to save the generated GroovyDoc in
272      * @throws ClassNotFoundException    when a class needed for GroovyDoc generation cannot be found
273      * @throws InstantiationException    when a class needed for GroovyDoc generation cannot be instantiated
274      * @throws IllegalAccessException    when a method needed for GroovyDoc generation cannot be accessed
275      * @throws InvocationTargetException when a reflection invocation needed for GroovyDoc generation cannot be completed
276      * @throws MalformedURLException     when a classpath element provides a malformed URL
277      */
278     protected synchronized void doGroovyDocGeneration(final FileSet[] sourceDirectories, final List<?> classpath, final File outputDirectory) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException, MalformedURLException {
279         if (skipGroovyDoc) {
280             getLog().info("Skipping generation of GroovyDoc because ${skipGroovydoc} was set to true.");
281             return;
282         }
283 
284         if (sourceDirectories == null || sourceDirectories.length == 0) {
285             getLog().info("No source directories specified for GroovyDoc generation. Skipping.");
286             return;
287         }
288 
289         GroovyDocConfiguration configuration = new GroovyDocConfiguration(sourceDirectories, classpath, outputDirectory);
290         configuration.setIncludeClasspath(includeClasspath);
291         configuration.setDocProperties(setupProperties());
292         configuration.setLinks(links);
293 
294         configuration.setAttachGroovyDocAnnotation(attachGroovyDocAnnotation);
295         configuration.setDefaultDocTemplates(defaultDocTemplates);
296         configuration.setDefaultPackageTemplates(defaultPackageTemplates);
297         configuration.setDefaultClassTemplates(defaultClassTemplates);
298         configuration.setGroovyDocToolClass(groovyDocToolClass);
299         configuration.setOutputToolClass(outputToolClass);
300         configuration.setFileOutputToolClass(fileOutputToolClass);
301         configuration.setResourceManagerClass(resourceManagerClass);
302         configuration.setClasspathResourceManagerClass(classpathResourceManagerClass);
303         configuration.setLinkArgumentClass(linkArgumentClass);
304         configuration.setWindowTitle(windowTitle);
305         configuration.setDocTitle(docTitle);
306         configuration.setFooter(footer);
307         configuration.setHeader(header);
308         configuration.setDisplayAuthor(displayAuthor);
309         configuration.setDisplayAuthor(displayAuthor);
310         configuration.setOverviewFile(overviewFile);
311         configuration.setLanguageLevel(languageLevel != null ? languageLevel.toString() : null);
312 
313         configuration.setScope(scope);
314 
315         org.apache.maven.toolchain.Toolchain toolchain = toolchainManager.getToolchainFromBuildContext("jdk", session);
316         if (toolchain != null) {
317             getLog().info("Toolchain in gmavenplus-plugin: " + toolchain);
318             performForkedGroovyDocGeneration(configuration, toolchain.findTool("java"));
319         } else if (fork) {
320             String javaExecutable = getJavaExecutable();
321             getLog().info("Forking GroovyDoc generation using " + javaExecutable);
322             performForkedGroovyDocGeneration(configuration, javaExecutable);
323         } else {
324             performInProcessGroovyDocGeneration(configuration);
325         }
326 
327         // overwrite stylesheet.css with provided stylesheet (if configured)
328         if (stylesheetFile != null) {
329             copyStylesheet(outputDirectory);
330         }
331     }
332 
333     protected void performInProcessGroovyDocGeneration(GroovyDocConfiguration configuration) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException, MalformedURLException {
334         setupClassWrangler(configuration.getClasspath(), includeClasspath);
335         classWrangler.logGroovyVersion(mojoExecution.getMojoDescriptor().getGoal());
336         logPluginClasspath();
337 
338         if (!groovyVersionSupportsAction()) {
339             getLog().error("Your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support GroovyDoc. The minimum version of Groovy required is " + minGroovyVersion + ". Skipping GroovyDoc generation.");
340             return;
341         }
342         if (groovyIs(GROOVY_1_6_0_RC1) || groovyIs(GROOVY_1_5_8)) {
343             // Groovy 1.5.8 and 1.6-RC-1 are blacklisted because of their dependency on org.apache.tools.ant.types.Path in GroovyDocTool constructor
344             getLog().warn("Groovy " + GROOVY_1_5_8 + " and " + GROOVY_1_6_0_RC1 + " are blacklisted from the supported GroovyDoc versions because of their dependency on Ant. Skipping GroovyDoc generation.");
345             return;
346         }
347 
348         GroovyCompiler compiler = new GroovyCompiler(classWrangler, getLog());
349         compiler.generateGroovyDoc(configuration);
350     }
351 
352     protected void performForkedGroovyDocGeneration(GroovyDocConfiguration configuration, String javaExecutable) throws InvocationTargetException {
353         try {
354              // Write configuration to file
355             File configFile = File.createTempFile("groovy-doc-config", ".ser");
356             configFile.deleteOnExit();
357             try (java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(java.nio.file.Files.newOutputStream(configFile.toPath()))) {
358                 oos.writeObject(configuration);
359             }
360 
361             // Build classpath for forked process (plugin + dependencies)
362             String forkClasspath = buildForkClasspath();
363 
364             List<String> command = new ArrayList<>();
365             command.add(javaExecutable);
366             command.add("-cp");
367             command.add(forkClasspath);
368             command.add("org.codehaus.gmavenplus.util.ForkedGroovyCompiler");
369             command.add(configFile.getAbsolutePath());
370 
371             ProcessBuilder pb = new ProcessBuilder(command);
372             pb.inheritIO();
373             Process process = pb.start();
374             int exitCode = process.waitFor();
375 
376             if (exitCode != 0) {
377                 throw new InvocationTargetException(new RuntimeException("Forked GroovyDoc generation failed with exit code " + exitCode));
378             }
379         } catch (IOException | InterruptedException e) {
380             throw new InvocationTargetException(e);
381         }
382     }
383 
384     protected String buildForkClasspath() {
385         StringBuilder cp = new StringBuilder();
386         // Add plugin artifact
387         cp.append(pluginDescriptor.getPluginArtifact().getFile().getAbsolutePath());
388 
389         // Add plugin dependencies
390         for (org.apache.maven.artifact.Artifact artifact : pluginDescriptor.getArtifacts()) {
391             cp.append(File.pathSeparator);
392             cp.append(artifact.getFile().getAbsolutePath());
393         }
394 
395         // Add maven-plugin-api jar which is 'provided' so not in getArtifacts()
396         try {
397             Class<?> logClass = org.apache.maven.plugin.logging.Log.class;
398             java.security.CodeSource codeSource = logClass.getProtectionDomain().getCodeSource();
399             if (codeSource != null) {
400                 String logJar = new File(codeSource.getLocation().toURI()).getAbsolutePath();
401                 cp.append(File.pathSeparator).append(logJar);
402             }
403         } catch (Exception e) {
404             getLog().warn("Could not find maven-plugin-api jar to add to fork classpath", e);
405         }
406 
407         return cp.toString();
408     }
409 
410     /**
411      * Sets up the documentation properties.
412      *
413      * @return the documentation properties
414      */
415     protected Properties setupProperties() {
416         Properties properties = new Properties();
417         properties.setProperty("windowTitle", windowTitle);
418         properties.setProperty("docTitle", docTitle);
419         properties.setProperty("footer", footer);
420         properties.setProperty("header", header);
421         properties.setProperty("author", Boolean.toString(displayAuthor));
422         properties.setProperty("overviewFile", overviewFile != null ? overviewFile.getAbsolutePath() : "");
423         try {
424             Scopes scopeVal = Scopes.valueOf(scope.toUpperCase());
425             if (scopeVal.equals(Scopes.PUBLIC)) {
426                 properties.setProperty("publicScope", "true");
427             } else if (scopeVal.equals(Scopes.PROTECTED)) {
428                 properties.setProperty("protectedScope", "true");
429             } else if (scopeVal.equals(Scopes.PACKAGE)) {
430                 properties.setProperty("packageScope", "true");
431             } else if (scopeVal.equals(Scopes.PRIVATE)) {
432                 properties.setProperty("privateScope", "true");
433             }
434         } catch (IllegalArgumentException e) {
435             getLog().warn("Scope (" + scope + ") was not recognized. Skipping argument.");
436         }
437 
438         return properties;
439     }
440 
441 
442     /**
443      * Copies the stylesheet to the specified output directory.
444      *
445      * @param outputDirectory The output directory to copy the stylesheet to
446      */
447     protected void copyStylesheet(final File outputDirectory) {
448         getLog().info("Using stylesheet from " + stylesheetFile.getAbsolutePath() + ".");
449         try {
450             BufferedReader bufferedReader = null;
451             BufferedWriter bufferedWriter = null;
452             try {
453                 if (stylesheetEncoding != null) {
454                     bufferedReader = new BufferedReader(new InputStreamReader(Files.newInputStream(stylesheetFile.toPath()), stylesheetEncoding));
455                 } else {
456                     bufferedReader = new BufferedReader(new InputStreamReader(Files.newInputStream(stylesheetFile.toPath())));
457                 }
458                 StringBuilder css = new StringBuilder();
459                 String line;
460                 while ((line = bufferedReader.readLine()) != null) {
461                     css.append(line).append("\n");
462                 }
463                 File outfile = new File(outputDirectory, "stylesheet.css");
464                 if (stylesheetEncoding != null) {
465                     bufferedWriter = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(outfile.toPath()), stylesheetEncoding));
466                 } else {
467                     bufferedWriter = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(outfile.toPath())));
468                 }
469                 bufferedWriter.write(css.toString());
470             } finally {
471                 FileUtils.closeQuietly(bufferedReader);
472                 FileUtils.closeQuietly(bufferedWriter);
473             }
474         } catch (IOException e) {
475             getLog().warn("Unable to copy specified stylesheet (" + stylesheetFile.getAbsolutePath() + ").");
476         }
477     }
478 
479 }