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