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