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.groovyworkarounds.GroovyDocTemplateInfo;
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 java.io.BufferedReader;
30  import java.io.BufferedWriter;
31  import java.io.File;
32  import java.io.IOException;
33  import java.io.InputStreamReader;
34  import java.io.OutputStreamWriter;
35  import java.lang.reflect.InvocationTargetException;
36  import java.lang.reflect.Method;
37  import java.net.MalformedURLException;
38  import java.nio.file.Files;
39  import java.util.ArrayList;
40  import java.util.List;
41  import java.util.Properties;
42  
43  import static org.codehaus.gmavenplus.util.ReflectionUtils.findConstructor;
44  import static org.codehaus.gmavenplus.util.ReflectionUtils.findMethod;
45  import static org.codehaus.gmavenplus.util.ReflectionUtils.invokeConstructor;
46  import static org.codehaus.gmavenplus.util.ReflectionUtils.invokeMethod;
47  
48  
49  /**
50   * The base GroovyDoc mojo, which all GroovyDoc mojos extend.
51   *
52   * @author Keegan Witt
53   * @since 1.0-beta-1
54   */
55  public abstract class AbstractGroovyDocMojo extends AbstractGroovySourcesMojo {
56  
57      /**
58       * Groovy 3.0.0 alpha-4 version.
59       */
60      protected static final Version GROOVY_3_0_0_ALPHA_4 = new Version(3, 0, 0, "alpha-4");
61  
62      /**
63       * Groovy 1.6.0 RC-2 version.
64       */
65      protected static final Version GROOVY_1_6_0_RC2 = new Version(1, 6, 0, "RC-2");
66  
67      /**
68       * Groovy 1.6.0 RC-1 version.
69       */
70      protected static final Version GROOVY_1_6_0_RC1 = new Version(1, 6, 0, "RC-1");
71  
72      /**
73       * Groovy 1.5.8 version.
74       */
75      protected static final Version GROOVY_1_5_8 = new Version(1, 5, 8);
76  
77      /**
78       * Groovy 1.5.2 version.
79       */
80      protected static final Version GROOVY_1_5_2 = new Version(1, 5, 2);
81  
82      /**
83       * The window title.
84       */
85      @Parameter(defaultValue = "Groovy Documentation")
86      protected String windowTitle;
87  
88      /**
89       * The page title.
90       */
91      @Parameter(defaultValue = "Groovy Documentation")
92      protected String docTitle;
93  
94      /**
95       * The page footer.
96       */
97      @Parameter(defaultValue = "Groovy Documentation")
98      protected String footer;
99  
100     /**
101      * The page header.
102      */
103     @Parameter(defaultValue = "Groovy Documentation")
104     protected String header;
105 
106     /**
107      * Whether to display the author in the generated GroovyDoc.
108      */
109     @Parameter(defaultValue = "true")
110     protected boolean displayAuthor;
111 
112     /**
113      * The HTML file to be used for overview documentation.
114      */
115     @Parameter
116     protected File overviewFile;
117 
118     /**
119      * The stylesheet file (absolute path) to copy to output directory (will overwrite default stylesheet.css).
120      */
121     @Parameter
122     protected File stylesheetFile;
123 
124     /**
125      * The encoding of stylesheetFile.
126      */
127     @Parameter(defaultValue = "${project.build.sourceEncoding}")
128     protected String stylesheetEncoding;
129 
130     /**
131      * The scope to generate GroovyDoc for. Should be one of:
132      * <ul>
133      *   <li>"public"</li>
134      *   <li>"protected"</li>
135      *   <li>"package"</li>
136      *   <li>"private"</li>
137      * </ul>
138      */
139     @Parameter(defaultValue = "private")
140     protected String scope;
141 
142     /**
143      * Links to include in the generated GroovyDoc (key is link href, value is comma-separated packages to use that link).
144      *
145      * @since 1.0-beta-2
146      */
147     @Parameter
148     protected List<Link> links;
149 
150     /**
151      * Flag to allow GroovyDoc generation to be skipped.
152      *
153      * @since 1.6
154      */
155     @Parameter(property = "skipGroovydoc", defaultValue = "false")
156     protected boolean skipGroovyDoc;
157 
158     /**
159      * What classpath to include. One of
160      * <ul>
161      *   <li>PROJECT_ONLY</li>
162      *   <li>PROJECT_AND_PLUGIN</li>
163      *   <li>PLUGIN_ONLY</li>
164      * </ul>
165      * Uses the same scope as the required dependency resolution of this mojo. Use only if you know what you're doing.
166      *
167      * @since 1.8.0
168      */
169     @Parameter(defaultValue = "PROJECT_ONLY")
170     protected IncludeClasspath includeClasspath;
171 
172     /**
173      * Override the default Groovydoc default top-level templates. Uses Groovy's standard templates by default.
174      *
175      * @since 1.10.1
176      */
177     @Parameter
178     protected String[] defaultDocTemplates = null;
179 
180     /**
181      * Override the default Groovydoc package-level templates. Uses Groovy's standard templates by default.
182      *
183      * @since 1.10.1
184      */
185     @Parameter
186     protected String[] defaultPackageTemplates = null;
187 
188     /**
189      * Override the default Groovydoc class-level templates. Uses Groovy's standard templates by default.
190      *
191      * @since 1.10.1
192      */
193     @Parameter
194     protected String[] defaultClassTemplates = null;
195 
196     /**
197      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.GroovyDocTool, for use when
198      * creating custom GroovyDoc implementations.
199      *
200      * @since 1.10.1
201      */
202     @Parameter
203     protected String groovyDocToolClass = null;
204 
205     /**
206      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.OutputTool, for use when
207      * creating custom GroovyDoc implementations.
208      *
209      * @since 1.10.1
210      */
211     @Parameter
212     protected String outputToolClass = null;
213 
214     /**
215      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.FileOutputTool, for use
216      * when creating custom GroovyDoc implementations.
217      *
218      * @since 1.10.1
219      */
220     @Parameter
221     protected String fileOutputToolClass = null;
222 
223     /**
224      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.ResourceManager, for use
225      * when creating custom GroovyDoc implementations.
226      *
227      * @since 1.10.1
228      */
229     @Parameter
230     protected String resourceManagerClass = null;
231 
232     /**
233      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.ClasspathResourceManager,
234      * for use when creating custom GroovyDoc implementations.
235      *
236      * @since 1.10.1
237      */
238     @Parameter
239     protected String classpathResourceManagerClass = null;
240 
241     /**
242      * Allows you to override the class that is normally org.codehaus.groovy.tools.groovydoc.LinkArgument (or
243      * org.codehaus.groovy.ant.Groovydoc$LinkArgument for Groovy older than 1.6-RC-2), for use when creating custom
244      * GroovyDoc implementations.
245      *
246      * @since 1.10.1
247      */
248     @Parameter
249     protected String linkArgumentClass = null;
250 
251     /**
252      * Enable attaching GroovyDoc annotation. Requires Groovy 3.0.0 alpha-4 or newer.
253      *
254      * @since 1.11.0
255      */
256     @Parameter(defaultValue = "false")
257     protected boolean attachGroovyDocAnnotation;
258 
259     /**
260      * Generates the GroovyDoc for the specified sources.
261      *
262      * @param sourceDirectories The source directories to generate GroovyDoc for
263      * @param classpath         The classpath to use for compilation
264      * @param outputDirectory   The directory to save the generated GroovyDoc in
265      * @throws ClassNotFoundException    when a class needed for GroovyDoc generation cannot be found
266      * @throws InstantiationException    when a class needed for GroovyDoc generation cannot be instantiated
267      * @throws IllegalAccessException    when a method needed for GroovyDoc generation cannot be accessed
268      * @throws InvocationTargetException when a reflection invocation needed for GroovyDoc generation cannot be completed
269      * @throws MalformedURLException     when a classpath element provides a malformed URL
270      */
271     protected synchronized void doGroovyDocGeneration(final FileSet[] sourceDirectories, final List<?> classpath, final File outputDirectory) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException, MalformedURLException {
272         if (skipGroovyDoc) {
273             getLog().info("Skipping generation of GroovyDoc because ${skipGroovydoc} was set to true.");
274             return;
275         }
276 
277         if (sourceDirectories == null || sourceDirectories.length == 0) {
278             getLog().info("No source directories specified for GroovyDoc generation. Skipping.");
279             return;
280         }
281 
282         setupClassWrangler(classpath, includeClasspath);
283 
284         classWrangler.logGroovyVersion(mojoExecution.getMojoDescriptor().getGoal());
285         logPluginClasspath();
286 
287         if (!groovyVersionSupportsAction()) {
288             getLog().error("Your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support GroovyDoc. The minimum version of Groovy required is " + minGroovyVersion + ". Skipping GroovyDoc generation.");
289             return;
290         }
291         if (groovyIs(GROOVY_1_6_0_RC1) || groovyIs(GROOVY_1_5_8)) {
292             // 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
293             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.");
294             return;
295         }
296 
297         // get classes we need with reflection
298         Class<?> groovyDocToolClass = classWrangler.getClass(this.groovyDocToolClass == null ? "org.codehaus.groovy.tools.groovydoc.GroovyDocTool" : this.groovyDocToolClass);
299         Class<?> outputToolClass = classWrangler.getClass(this.outputToolClass == null ? "org.codehaus.groovy.tools.groovydoc.OutputTool" : this.outputToolClass);
300         Class<?> fileOutputToolClass = classWrangler.getClass(this.fileOutputToolClass == null ? "org.codehaus.groovy.tools.groovydoc.FileOutputTool" : this.fileOutputToolClass);
301         Class<?> resourceManagerClass = classWrangler.getClass(this.resourceManagerClass == null ? "org.codehaus.groovy.tools.groovydoc.ResourceManager" : this.resourceManagerClass);
302         Class<?> classpathResourceManagerClass = classWrangler.getClass(this.classpathResourceManagerClass == null ? "org.codehaus.groovy.tools.groovydoc.ClasspathResourceManager" : this.classpathResourceManagerClass);
303 
304         // set up GroovyDoc options
305         if (attachGroovyDocAnnotation) {
306             if (groovyAtLeast(GROOVY_3_0_0_ALPHA_4)) {
307                 System.setProperty("runtimeGroovydoc", "true");
308             } else {
309                 getLog().warn("Requested to enable attaching GroovyDoc annotation, but your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support it (must be " + GROOVY_3_0_0_ALPHA_4 + " or newer). Ignoring enableGroovyDocAnnotation parameter.");
310             }
311         }
312         Properties docProperties = setupProperties();
313         Object fileOutputTool = invokeConstructor(findConstructor(fileOutputToolClass));
314         Object classpathResourceManager = invokeConstructor(findConstructor(classpathResourceManagerClass));
315         FileSetManager fileSetManager = new FileSetManager();
316         List<String> sourceDirectoriesStrings = new ArrayList<>();
317         for (FileSet sourceDirectory : sourceDirectories) {
318             sourceDirectoriesStrings.add(sourceDirectory.getDirectory());
319         }
320         GroovyDocTemplateInfo groovyDocTemplateInfo = new GroovyDocTemplateInfo(classWrangler.getGroovyVersion());
321         List<?> groovyDocLinks = setupLinks();
322         if (groovyOlderThan(GROOVY_1_6_0_RC2)) {
323             getLog().warn("Your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support GroovyDoc documentation properties (docTitle, footer, header, displayAuthor, overviewFile, and scope). You need Groovy 1.6-RC-2 or newer to support this. Ignoring properties.");
324         }
325 
326         // prevent Java stubs (which lack Javadoc) from overwriting GroovyDoc by removing Java sources
327         List<String> groovyDocSources = setupGroovyDocSources(sourceDirectories, fileSetManager);
328 
329         // instantiate GroovyDocTool
330         Object groovyDocTool = createGroovyDocTool(groovyDocToolClass, resourceManagerClass, docProperties, classpathResourceManager, sourceDirectoriesStrings, groovyDocTemplateInfo, groovyDocLinks);
331 
332         // generate GroovyDoc
333         generateGroovyDoc(outputDirectory, groovyDocToolClass, outputToolClass, fileOutputTool, groovyDocSources, groovyDocTool);
334 
335         // overwrite stylesheet.css with provided stylesheet (if configured)
336         if (stylesheetFile != null) {
337             copyStylesheet(outputDirectory);
338         }
339     }
340 
341     /**
342      * Sets up the documentation properties.
343      *
344      * @return the documentation properties
345      */
346     protected Properties setupProperties() {
347         Properties properties = new Properties();
348         properties.setProperty("windowTitle", windowTitle);
349         properties.setProperty("docTitle", docTitle);
350         properties.setProperty("footer", footer);
351         properties.setProperty("header", header);
352         properties.setProperty("author", Boolean.toString(displayAuthor));
353         properties.setProperty("overviewFile", overviewFile != null ? overviewFile.getAbsolutePath() : "");
354         try {
355             Scopes scopeVal = Scopes.valueOf(scope.toUpperCase());
356             if (scopeVal.equals(Scopes.PUBLIC)) {
357                 properties.setProperty("publicScope", "true");
358             } else if (scopeVal.equals(Scopes.PROTECTED)) {
359                 properties.setProperty("protectedScope", "true");
360             } else if (scopeVal.equals(Scopes.PACKAGE)) {
361                 properties.setProperty("packageScope", "true");
362             } else if (scopeVal.equals(Scopes.PRIVATE)) {
363                 properties.setProperty("privateScope", "true");
364             }
365         } catch (IllegalArgumentException e) {
366             getLog().warn("Scope (" + scope + ") was not recognized. Skipping argument.");
367         }
368 
369         return properties;
370     }
371 
372     /**
373      * Sets up the GroovyDoc links.
374      *
375      * @return the GroovyDoc links
376      * @throws ClassNotFoundException    when a class needed for setting up GroovyDoc links cannot be found
377      * @throws InstantiationException    when a class needed for setting up GroovyDoc links cannot be instantiated
378      * @throws IllegalAccessException    when a method needed for setting up GroovyDoc links cannot be accessed
379      * @throws InvocationTargetException when a reflection invocation needed for setting up GroovyDoc links cannot be completed
380      */
381     @SuppressWarnings({"rawtypes", "unchecked"})
382     protected List<?> setupLinks() throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException {
383         List linksList = new ArrayList();
384         if (links != null && !links.isEmpty()) {
385             Class<?> linkArgumentClass = null;
386             if (this.linkArgumentClass == null) {
387                 if (groovyAtLeast(GROOVY_1_6_0_RC2)) {
388                     linkArgumentClass = classWrangler.getClass("org.codehaus.groovy.tools.groovydoc.LinkArgument");
389                 } else if (groovyAtLeast(GROOVY_1_5_2)) {
390                     linkArgumentClass = classWrangler.getClass("org.codehaus.groovy.ant.Groovydoc$LinkArgument");
391                 }
392             } else {
393                 linkArgumentClass = classWrangler.getClass(this.linkArgumentClass);
394             }
395             if (linkArgumentClass != null) {
396                 Method setHref = findMethod(linkArgumentClass, "setHref", String.class);
397                 Method setPackages = findMethod(linkArgumentClass, "setPackages", String.class);
398                 for (Link link : links) {
399                     Object linkArgument = invokeConstructor(findConstructor(linkArgumentClass));
400                     invokeMethod(setHref, linkArgument, link.getHref());
401                     invokeMethod(setPackages, linkArgument, link.getPackages());
402                     linksList.add(linkArgument);
403                 }
404             } else {
405                 getLog().warn("Requested to use GroovyDoc links, but your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support it (must be 1.5.2 or newer). Ignoring links parameter.");
406             }
407         }
408 
409         return linksList;
410     }
411 
412     /**
413      * Instantiates a new GroovyDocTool.
414      *
415      * @param groovyDocToolClass       the GroovyDocTool class
416      * @param resourceManagerClass     the ResourceManager lass
417      * @param docProperties            the documentation properties
418      * @param classpathResourceManager the ClasspathResourceManager for the GroovyDocTool
419      * @param sourceDirectories        the source directories for the GroovyDocTool
420      * @param groovyDocTemplateInfo    the GroovyDocTemplateInfo for the GroovyDocTool
421      * @param groovyDocLinks           the GroovyDoc links
422      * @return the GroovyDocTool to use in GroovyDoc generation
423      * @throws InstantiationException    when a class needed for setting up GroovyDoc tool cannot be instantiated
424      * @throws IllegalAccessException    when a method needed for setting up GroovyDoc tool cannot be accessed
425      * @throws InvocationTargetException when a reflection invocation needed for setting up GroovyDoc tool cannot be completed
426      */
427     protected Object createGroovyDocTool(final Class<?> groovyDocToolClass, final Class<?> resourceManagerClass, final Properties docProperties, final Object classpathResourceManager, final List<String> sourceDirectories, final GroovyDocTemplateInfo groovyDocTemplateInfo, final List<?> groovyDocLinks) throws InvocationTargetException, IllegalAccessException, InstantiationException {
428         Object groovyDocTool;
429         if (groovyAtLeast(GROOVY_1_6_0_RC2)) {
430             groovyDocTool = invokeConstructor(findConstructor(groovyDocToolClass, resourceManagerClass, String[].class, String[].class, String[].class, String[].class, List.class, Properties.class),
431                     classpathResourceManager,
432                     sourceDirectories.toArray(new String[0]),
433                     defaultDocTemplates == null ? groovyDocTemplateInfo.defaultDocTemplates() : defaultDocTemplates,
434                     defaultPackageTemplates == null ? groovyDocTemplateInfo.defaultPackageTemplates() : defaultPackageTemplates,
435                     defaultClassTemplates == null ? groovyDocTemplateInfo.defaultClassTemplates() : defaultClassTemplates,
436                     groovyDocLinks,
437                     docProperties
438             );
439         } else if (groovyAtLeast(GROOVY_1_5_2)) {
440             groovyDocTool = invokeConstructor(findConstructor(groovyDocToolClass, resourceManagerClass, String.class, String[].class, String[].class, String[].class, List.class),
441                     classpathResourceManager,
442                     sourceDirectories.get(0),
443                     defaultDocTemplates == null ? groovyDocTemplateInfo.defaultDocTemplates() : defaultDocTemplates,
444                     defaultPackageTemplates == null ? groovyDocTemplateInfo.defaultPackageTemplates() : defaultPackageTemplates,
445                     defaultClassTemplates == null ? groovyDocTemplateInfo.defaultClassTemplates() : defaultClassTemplates,
446                     groovyDocLinks
447             );
448             if (sourceDirectories.size() > 1) {
449                 getLog().warn("Your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support more than one GroovyDoc source directory (must be 1.6-RC-2 or newer). Only using first source directory (" + sourceDirectories.get(0) + ").");
450             }
451         } else {
452             groovyDocTool = invokeConstructor(findConstructor(groovyDocToolClass, resourceManagerClass, String.class, String[].class, String[].class, String[].class),
453                     classpathResourceManager,
454                     sourceDirectories.get(0),
455                     defaultDocTemplates == null ? groovyDocTemplateInfo.defaultDocTemplates() : defaultDocTemplates,
456                     defaultPackageTemplates == null ? groovyDocTemplateInfo.defaultPackageTemplates() : defaultPackageTemplates,
457                     defaultClassTemplates == null ? groovyDocTemplateInfo.defaultClassTemplates() : defaultClassTemplates
458             );
459             if (sourceDirectories.size() > 1) {
460                 getLog().warn("Your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support more than one GroovyDoc source directory (must be 1.6-RC-2 or newer). Only using first source directory (" + sourceDirectories.get(0) + ").");
461             }
462         }
463 
464         return groovyDocTool;
465     }
466 
467     /**
468      * Gets the Groovy sources without the Java sources (since the Java sources don't have Javadoc).
469      *
470      * @param sourceDirectories the source directories to get the Groovy sources from
471      * @param fileSetManager    the FileSetmanager to use to get the included files
472      * @return the groovy sources
473      */
474     protected List<String> setupGroovyDocSources(final FileSet[] sourceDirectories, final FileSetManager fileSetManager) {
475         List<String> javaSources = new ArrayList<>();
476         List<String> groovySources = new ArrayList<>();
477         List<String> possibleGroovyStubs = new ArrayList<>();
478         for (FileSet sourceDirectory : sourceDirectories) {
479             String[] sources = fileSetManager.getIncludedFiles(sourceDirectory);
480             for (String source : sources) {
481                 if (source.endsWith(".java") && !javaSources.contains(source)) {
482                     javaSources.add(source);
483                 } else if (!groovySources.contains(source)) {
484                     groovySources.add(source);
485                     possibleGroovyStubs.add(source.replaceFirst("\\." + FileUtils.getFileExtension(source), ".java"));
486                 }
487             }
488         }
489         javaSources.removeAll(possibleGroovyStubs);
490         List<String> groovyDocSources = new ArrayList<>();
491         groovyDocSources.addAll(javaSources);
492         groovyDocSources.addAll(groovySources);
493 
494         return groovyDocSources;
495     }
496 
497     /**
498      * Performs the GroovyDoc generation.
499      *
500      * @param outputDirectory    the directory to output the GroovyDoc to
501      * @param groovyDocToolClass the GroovyDocTool class
502      * @param outputToolClass    the OutputTool class
503      * @param fileOutputTool     the FileOutputTool to use for GroovyDoc generation
504      * @param groovyDocSources   the sources to
505      * @param groovyDocTool      the GroovyDocTool to use for GroovyDoc generation
506      * @throws IllegalAccessException    when a method needed for GroovyDoc generation cannot be accessed
507      * @throws InvocationTargetException when a reflection invocation needed for GroovyDoc generation cannot be completed
508      */
509     protected void generateGroovyDoc(final File outputDirectory, final Class<?> groovyDocToolClass, final Class<?> outputToolClass, final Object fileOutputTool, final List<String> groovyDocSources, final Object groovyDocTool) throws InvocationTargetException, IllegalAccessException {
510         getLog().debug("Adding sources to generate GroovyDoc for:");
511         if (getLog().isDebugEnabled()) {
512             for (String groovyDocSource : groovyDocSources) {
513                 getLog().debug("    " + groovyDocSource);
514             }
515         }
516         if (groovyAtLeast(GROOVY_1_6_0_RC2)) {
517             invokeMethod(findMethod(groovyDocToolClass, "add", List.class), groovyDocTool, groovyDocSources);
518         } else {
519             Method add = findMethod(groovyDocToolClass, "add", String.class);
520             for (String groovyDocSource : groovyDocSources) {
521                 invokeMethod(add, groovyDocTool, groovyDocSource);
522             }
523         }
524         invokeMethod(findMethod(groovyDocToolClass, "renderToOutput", outputToolClass, String.class), groovyDocTool, fileOutputTool, outputDirectory.getAbsolutePath());
525     }
526 
527     /**
528      * Copies the stylesheet to the specified output directory.
529      *
530      * @param outputDirectory The output directory to copy the stylesheet to
531      */
532     protected void copyStylesheet(final File outputDirectory) {
533         getLog().info("Using stylesheet from " + stylesheetFile.getAbsolutePath() + ".");
534         try {
535             BufferedReader bufferedReader = null;
536             BufferedWriter bufferedWriter = null;
537             try {
538                 if (stylesheetEncoding != null) {
539                     bufferedReader = new BufferedReader(new InputStreamReader(Files.newInputStream(stylesheetFile.toPath()), stylesheetEncoding));
540                 } else {
541                     bufferedReader = new BufferedReader(new InputStreamReader(Files.newInputStream(stylesheetFile.toPath())));
542                 }
543                 StringBuilder css = new StringBuilder();
544                 String line;
545                 while ((line = bufferedReader.readLine()) != null) {
546                     css.append(line).append("\n");
547                 }
548                 File outfile = new File(outputDirectory, "stylesheet.css");
549                 if (stylesheetEncoding != null) {
550                     bufferedWriter = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(outfile.toPath()), stylesheetEncoding));
551                 } else {
552                     bufferedWriter = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(outfile.toPath())));
553                 }
554                 bufferedWriter.write(css.toString());
555             } finally {
556                 FileUtils.closeQuietly(bufferedReader);
557                 FileUtils.closeQuietly(bufferedWriter);
558             }
559         } catch (IOException e) {
560             getLog().warn("Unable to copy specified stylesheet (" + stylesheetFile.getAbsolutePath() + ").");
561         }
562     }
563 
564 }