View Javadoc

1   package org.apache.maven.plugin.jxr;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.net.URL;
25  import java.util.ArrayList;
26  import java.util.Calendar;
27  import java.util.Collection;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.ResourceBundle;
32  
33  import org.apache.maven.doxia.siterenderer.Renderer;
34  import org.apache.maven.jxr.JXR;
35  import org.apache.maven.jxr.JxrException;
36  import org.apache.maven.model.Organization;
37  import org.apache.maven.model.ReportPlugin;
38  import org.apache.maven.plugin.MojoExecutionException;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.maven.reporting.AbstractMavenReport;
41  import org.apache.maven.reporting.MavenReportException;
42  import org.codehaus.plexus.util.FileUtils;
43  import org.codehaus.plexus.util.ReaderFactory;
44  import org.codehaus.plexus.util.StringUtils;
45  
46  /**
47   * Base class for the JXR reports.
48   *
49   * @author <a href="mailto:bellingard.NO-SPAM@gmail.com">Fabrice Bellingard</a>
50   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
51   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
52   * @version $Id: AbstractJxrReport.java 1160829 2011-08-23 18:12:48Z bimargulies $
53   */
54  public abstract class AbstractJxrReport
55      extends AbstractMavenReport
56  {
57      /**
58       * @parameter expression="${project}"
59       * @required
60       * @readonly
61       */
62      private MavenProject project;
63  
64      /**
65       * @component
66       */
67      private Renderer siteRenderer;
68  
69      /**
70       * Output folder where the main page of the report will be generated. Note that this parameter is only relevant if
71       * the goal is run directly from the command line or from the default lifecycle. If the goal is run indirectly as
72       * part of a site generation, the output directory configured in the Maven Site Plugin will be used instead.
73       *
74       * @parameter expression="${project.reporting.outputDirectory}"
75       * @required
76       */
77      private File outputDirectory;
78  
79      /**
80       * File input encoding.
81       *
82       * @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}"
83       */
84      private String inputEncoding;
85  
86      /**
87       * File output encoding.
88       *
89       * @parameter expression="${outputEncoding}" default-value="${project.reporting.outputEncoding}"
90       */
91      private String outputEncoding;
92  
93      /**
94       * Title of window of the Xref HTML files.
95       *
96       * @parameter expression="${project.name} ${project.version} Reference"
97       */
98      private String windowTitle;
99  
100     /**
101      * Title of main page of the Xref HTML files.
102      *
103      * @parameter expression="${project.name} ${project.version} Reference"
104      */
105     private String docTitle;
106 
107     /**
108      * String uses at the bottom of the Xref HTML files.
109      *
110      * @parameter expression="${bottom}" default-value="Copyright &#169; {inceptionYear}-{currentYear} {projectOrganizationName}. All Rights Reserved."
111      */
112     private String bottom;
113 
114     /**
115      * Directory where Velocity templates can be found to generate overviews,
116      * frames and summaries.
117      * Should not be used. If used, should be an absolute path, like <code>"${basedir}/myTemplates"</code>.
118      *
119      * @parameter default-value="templates"
120      */
121     private String templateDir;
122 
123     /**
124      * Style sheet used for the Xref HTML files.
125      * Should not be used. If used, should be an absolute path, like <code>"${basedir}/myStyles.css"</code>.
126      *
127      * @parameter default-value="stylesheet.css"
128      */
129     private String stylesheet;
130 
131     /**
132      * A list of exclude patterns to use. By default no files are excluded.
133      *
134      * @parameter expression="${excludes}"
135      * @since 2.1
136      */
137     private ArrayList excludes;
138 
139     /**
140      * A list of include patterns to use. By default all .java files are included.
141      *
142      * @parameter expression="${includes}"
143      * @since 2.1
144      */
145     private ArrayList includes;
146 
147     /**
148      * The projects in the reactor for aggregation report.
149      *
150      * @parameter expression="${reactorProjects}"
151      * @readonly
152      */
153     protected List reactorProjects;
154 
155     /**
156      * Whether to build an aggregated report at the root, or build individual reports.
157      *
158      * @parameter expression="${aggregate}" default-value="false"
159      * @deprecated since 2.3. Use the goals <code>jxr:aggregate</code> and <code>jxr:test-aggregate</code> instead.
160      */
161     protected boolean aggregate;
162     
163     /**
164      * Whether to skip this execution.
165      * 
166      * @parameter expression="${maven.jxr.skip}" default-value="false"
167      * @since 2.3
168      */
169     protected boolean skip;
170 
171     /**
172      * Link the Javadoc from the Source XRef. Defaults to true and will link
173      * automatically if javadoc plugin is being used.
174      *
175      * @parameter expression="${linkJavadoc}" default-value="true"
176      */
177     private boolean linkJavadoc;
178 
179     /**
180      * Gets the effective reporting output files encoding.
181      *
182      * @return The effective reporting output file encoding, never <code>null</code>: defaults to
183      * <code>UTF-8</code> instead.
184      */
185     protected String getOutputEncoding()
186     {
187         return ( outputEncoding == null ) ? ReaderFactory.UTF_8 : outputEncoding;
188     }
189 
190     /**
191      * Compiles the list of directories which contain source files that will be included in the JXR report generation.
192      *
193      * @param sourceDirs the List of the source directories
194      * @return a List of the directories that will be included in the JXR report generation
195      */
196     protected List pruneSourceDirs( List sourceDirs )
197     {
198         List pruned = new ArrayList( sourceDirs.size() );
199         for ( Iterator i = sourceDirs.iterator(); i.hasNext(); )
200         {
201             String dir = (String) i.next();
202             if ( !pruned.contains( dir ) && hasSources( new File( dir ) ) )
203             {
204                 pruned.add( dir );
205             }
206         }
207         return pruned;
208     }
209 
210     /**
211      * Initialize some attributes required during the report generation
212      */
213     protected void init()
214     {
215         // wanna know if Javadoc is being generated
216         // TODO: what if it is not part of the site though, and just on the command line?
217         Collection plugin = project.getReportPlugins();
218         if ( plugin != null )
219         {
220             for ( Iterator iter = plugin.iterator(); iter.hasNext(); )
221             {
222                 ReportPlugin reportPlugin = (ReportPlugin) iter.next();
223                 if ( "maven-javadoc-plugin".equals( reportPlugin.getArtifactId() ) )
224                 {
225                     break;
226                 }
227             }
228         }
229     }
230 
231     /**
232      * Checks whether the given directory contains Java files.
233      *
234      * @param dir the source directory
235      * @return true if the folder or one of its subfolders contains at least 1 Java file
236      */
237     private boolean hasSources( File dir )
238     {
239         if ( dir.exists() && dir.isDirectory() )
240         {
241             File[] files = dir.listFiles();
242             for ( int i = 0; i < files.length; i++ )
243             {
244                 File currentFile = files[i];
245                 if ( currentFile.isFile() )
246                 {
247                     if ( currentFile.getName().endsWith( ".java" ) )
248                     {
249                         return true;
250                     }
251                 }
252                 else
253                 {
254                     if ( Character.isJavaIdentifierStart( currentFile.getName().charAt( 0 ) ) // avoid .svn directory
255                         && hasSources( currentFile ) )
256                     {
257                         return true;
258                     }
259                 }
260             }
261         }
262         return false;
263     }
264 
265     /**
266      * Creates the Xref for the Java files found in the given source directory and puts
267      * them in the given destination directory.
268      *
269      * @param locale               The user locale to use for the Xref generation
270      * @param destinationDirectory The output folder
271      * @param sourceDirs           The source directories
272      * @throws java.io.IOException
273      * @throws org.apache.maven.jxr.JxrException
274      *
275      */
276     private void createXref( Locale locale, String destinationDirectory, List sourceDirs )
277         throws IOException, JxrException
278     {
279         JXR jxr = new JXR();
280         jxr.setDest( destinationDirectory );
281         if ( StringUtils.isEmpty( inputEncoding ) )
282         {
283             String platformEncoding = System.getProperty( "file.encoding" );
284             getLog().warn( "File encoding has not been set, using platform encoding " + platformEncoding
285                            + ", i.e. build is platform dependent!" );
286         }
287         jxr.setInputEncoding( inputEncoding );
288         jxr.setLocale( locale );
289         jxr.setLog( new PluginLogAdapter( getLog() ) );
290         jxr.setOutputEncoding( getOutputEncoding() );
291         jxr.setRevision( "HEAD" );
292         jxr.setJavadocLinkDir( getJavadocLocation() );
293         // Set include/exclude patterns on the jxr instance
294         if ( excludes != null && !excludes.isEmpty() )
295         {
296             jxr.setExcludes( (String[]) excludes.toArray( new String[0] ) );
297         }
298         if ( includes != null && !includes.isEmpty() )
299         {
300             jxr.setIncludes( (String[]) includes.toArray( new String[0] ) );
301         }
302         
303         // avoid winding up using Velocity in two class loaders.
304         ClassLoader savedTccl = Thread.currentThread().getContextClassLoader();
305         try {
306             Thread.currentThread().setContextClassLoader( getClass().getClassLoader() );
307             jxr.xref( sourceDirs, templateDir, windowTitle, docTitle, getBottomText( project.getInceptionYear(), project
308                                                                                      .getOrganization() ) );
309         } finally {
310             Thread.currentThread().setContextClassLoader( savedTccl );
311         }
312 
313         // and finally copy the stylesheet
314         copyRequiredResources( destinationDirectory );
315     }
316 
317     /**
318      * Get the bottom text to be displayed at the lower part of the generated JXR reports.
319      *
320      * @param inceptionYear the year when the project was started
321      * @param organization the organization for the project
322      * @return  a String that contains the bottom text to be displayed in the lower part of the generated JXR reports
323      */
324     private String getBottomText( String inceptionYear, Organization organization )
325     {
326         int actualYear = Calendar.getInstance().get( Calendar.YEAR );
327         String year = String.valueOf( actualYear );
328 
329         String bottom = StringUtils.replace( this.bottom, "{currentYear}", year );
330 
331         if ( inceptionYear == null )
332         {
333             bottom = StringUtils.replace( bottom, "{inceptionYear}-", "" );
334         }
335         else
336         {
337             if ( inceptionYear.equals( year ) )
338             {
339                 bottom = StringUtils.replace( bottom, "{inceptionYear}-", "" );
340             }
341             else
342             {
343                 bottom = StringUtils.replace( bottom, "{inceptionYear}", inceptionYear );
344             }
345         }
346 
347         if ( organization != null && StringUtils.isNotEmpty( organization.getName() ) )
348         {
349             bottom = StringUtils.replace( bottom, "{projectOrganizationName}", organization.getName() );
350         }
351         else
352         {
353             bottom = StringUtils.replace( bottom, " {projectOrganizationName}", "" );
354         }
355 
356         return bottom;
357     }
358 
359     /**
360      * Copy some required resources (like the stylesheet) to the
361      * given directory
362      *
363      * @param dir the directory to copy the resources to
364      */
365     private void copyRequiredResources( String dir )
366     {
367         File stylesheetFile = new File( stylesheet );
368         File destStylesheetFile = new File( dir, "stylesheet.css" );
369 
370         try
371         {
372             if ( stylesheetFile.isAbsolute() )
373             {
374                 FileUtils.copyFile( stylesheetFile, destStylesheetFile );
375             }
376             else
377             {
378                 URL stylesheetUrl = this.getClass().getClassLoader().getResource( stylesheet );
379                 FileUtils.copyURLToFile( stylesheetUrl, destStylesheetFile );
380             }
381         }
382         catch ( IOException e )
383         {
384             getLog().warn( "An error occured while copying the stylesheet to the target directory", e );
385         }
386 
387     }
388 
389     /**
390      * @see org.apache.maven.reporting.AbstractMavenReport#getSiteRenderer()
391      */
392     protected Renderer getSiteRenderer()
393     {
394         return siteRenderer;
395     }
396 
397     /**
398      * @see org.apache.maven.reporting.AbstractMavenReport#getOutputDirectory()
399      */
400     protected String getOutputDirectory()
401     {
402         return outputDirectory.getAbsolutePath();
403     }
404 
405     /**
406      * @see org.apache.maven.reporting.AbstractMavenReport#getProject()
407      */
408     public MavenProject getProject()
409     {
410         return project;
411     }
412 
413     /**
414      * Returns the correct resource bundle according to the locale
415      *
416      * @param locale the locale of the user
417      * @return the bundle corresponding to the locale
418      */
419     protected ResourceBundle getBundle( Locale locale )
420     {
421         return ResourceBundle.getBundle( "jxr-report", locale, this.getClass().getClassLoader() );
422     }
423 
424     /**
425      * @param sourceDirs
426      * @return true if the report could be generated
427      */
428     protected boolean canGenerateReport( List sourceDirs )
429     {
430         boolean canGenerate = !sourceDirs.isEmpty();
431 
432         if ( isAggregate() && !project.isExecutionRoot() )
433         {
434             canGenerate = false;
435         }
436         return canGenerate;
437     }
438 
439     /* 
440      * This is called for a standalone execution. Well, that's the claim. It also ends up called for the aggregate mojo, since 
441      * that is configured as an execution, not in the reporting section, at least by some people on some days. We do NOT want
442      * the default behavior.
443      */
444     public void execute()
445         throws MojoExecutionException
446     {
447         
448         if ( skip )
449         {
450             getLog().info( "Skipping JXR." );
451             return;
452         }
453         
454         Locale locale = Locale.getDefault();
455         try
456         {
457             executeReport( locale );
458         }
459         catch ( MavenReportException e )
460         {
461             throw new MojoExecutionException( "Error generating JXR report", e );
462         }
463     }
464 
465     /**
466      * @see org.apache.maven.reporting.AbstractMavenReport#executeReport(java.util.Locale)
467      */
468     protected void executeReport( Locale locale )
469         throws MavenReportException
470     {
471         if ( skip )
472         {
473             getLog().info( "Skipping JXR." );
474             return;
475         }
476         List sourceDirs = constructSourceDirs();
477         if ( canGenerateReport( sourceDirs ) )
478         {
479             // init some attributes -- TODO (javadoc)
480             init();
481 
482             try
483             {
484                 createXref( locale, getDestinationDirectory(), sourceDirs );
485             }
486             catch ( JxrException e )
487             {
488                 throw new MavenReportException( "Error while generating the HTML source code of the projet.", e );
489             }
490             catch ( IOException e )
491             {
492                 throw new MavenReportException( "Error while generating the HTML source code of the projet.", e );
493             }
494         }
495     }
496 
497     /**
498      * Gets the list of the source directories to be included in the JXR report generation
499      *
500      * @return a List of the source directories whose contents will be included in the JXR report generation
501      */
502     protected List constructSourceDirs()
503     {
504         List sourceDirs = new ArrayList( getSourceRoots() );
505         if ( isAggregate() )
506         {
507             for ( Iterator i = reactorProjects.iterator(); i.hasNext(); )
508             {
509                 MavenProject project = (MavenProject) i.next();
510 
511                 if ( "java".equals( project.getArtifact().getArtifactHandler().getLanguage() ) )
512                 {
513                     sourceDirs.addAll( getSourceRoots( project ) );
514                 }
515             }
516         }
517 
518         sourceDirs = pruneSourceDirs( sourceDirs );
519         return sourceDirs;
520     }
521 
522     /**
523      * @see org.apache.maven.reporting.AbstractMavenReport#canGenerateReport()
524      */
525     public boolean canGenerateReport()
526     {
527         return canGenerateReport( constructSourceDirs() );
528     }
529 
530     /**
531      * @see org.apache.maven.reporting.AbstractMavenReport#isExternalReport()
532      */
533     public boolean isExternalReport()
534     {
535         return true;
536     }
537 
538     /**
539      * @return a String that contains the location of the javadocs
540      */
541     private String getJavadocLocation()
542         throws IOException
543     {
544         String location = null;
545         if ( linkJavadoc )
546         {
547             // We don't need to do the whole translation thing like normal, because JXR does it internally.
548             // It probably shouldn't.
549             if ( getJavadocDir().exists() )
550             {
551                 // XRef was already generated by manual execution of a lifecycle binding
552                 location = getJavadocDir().getAbsolutePath();
553             }
554             else
555             {
556                 // Not yet generated - check if the report is on its way
557 
558                 // Special case: using the site:stage goal
559                 String stagingDirectory = System.getProperty( "stagingDirectory" );
560 
561                 if ( StringUtils.isNotEmpty( stagingDirectory ) )
562                 {
563                     String javadocDestDir = getJavadocDir().getName();
564                     boolean javadocAggregate = JxrReportUtil.isJavadocAggregated( project );
565                     String structureProject = JxrReportUtil.getStructure( project, false );
566 
567                     if ( isAggregate() && javadocAggregate )
568                     {
569                         File outputDirectory = new File( stagingDirectory, structureProject );
570                         location = outputDirectory + "/" + javadocDestDir;
571                     }
572                     if ( !isAggregate() && javadocAggregate )
573                     {
574                         location = stagingDirectory + "/" + javadocDestDir;
575 
576                         String hierarchy = project.getName();
577 
578                         MavenProject parent = project.getParent();
579                         while ( parent != null )
580                         {
581                             hierarchy = parent.getName();
582                             parent = parent.getParent();
583                         }
584                         File outputDirectory = new File( stagingDirectory, hierarchy );
585                         location = outputDirectory + "/" + javadocDestDir;
586                     }
587                     if ( isAggregate() && !javadocAggregate )
588                     {
589                         getLog().warn(
590                                        "The JXR plugin is configured to build an aggregated report at the root, "
591                                            + "not the Javadoc plugin." );
592                     }
593                     if ( !isAggregate() && !javadocAggregate )
594                     {
595                         location = stagingDirectory + "/" + structureProject + "/" + javadocDestDir;
596                     }
597                 }
598                 else
599                 {
600                     location = getJavadocDir().getAbsolutePath();
601                 }
602             }
603 
604             if ( location == null )
605             {
606                 getLog().warn( "Unable to locate Javadoc to link to - DISABLED" );
607             }
608         }
609 
610         return location;
611     }
612 
613     /**
614      * Abstract method that returns the target directory where the generated JXR reports will be put.
615      *
616      * @return  a String that contains the target directory name
617      */
618     protected abstract String getDestinationDirectory();
619 
620     /**
621      * Abstract method that returns the specified source directories that will be included in the JXR report generation.
622      *
623      * @return a List of the source directories
624      */
625     protected abstract List getSourceRoots();
626 
627     /**
628      * Abstract method that returns the compile source directories of the specified project that will be included in the
629      * JXR report generation
630      *
631      * @param project the MavenProject where the JXR report plugin will be executed
632      * @return a List of the source directories
633      */
634     protected abstract List getSourceRoots( MavenProject project );
635 
636     /**
637      * Abstract method that returns the directory of the javadoc files.
638      *
639      * @return a File for the directory of the javadocs
640      */
641     protected abstract File getJavadocDir();
642 
643     /**
644      * Is the current report aggregated?
645      * @return
646      */
647     protected boolean isAggregate()
648     {
649         return aggregate;
650     }
651 }