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