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