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 © {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 }