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.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 710182 2008-11-03 21:53:34Z hboutemy $
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 uses at the bottom of the Xref HTML files.
108 *
109 * @parameter expression="${bottom}" default-value="Copyright © {inceptionYear}-{currentYear} {projectOrganizationName}. 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 */
159 protected boolean aggregate;
160
161 /**
162 * Link the Javadoc from the Source XRef. Defaults to true and will link
163 * automatically if javadoc plugin is being used.
164 *
165 * @parameter expression="${linkJavadoc}" default-value="true"
166 */
167 private boolean linkJavadoc;
168
169 /**
170 * Gets the effective reporting output files encoding.
171 *
172 * @return The effective reporting output file encoding, never <code>null</code>: defaults to
173 * <code>UTF-8</code> instead.
174 */
175 protected String getOutputEncoding()
176 {
177 return ( outputEncoding == null ) ? ReaderFactory.UTF_8 : outputEncoding;
178 }
179
180 /**
181 * Compiles the list of directories which contain source files that will be included in the JXR report generation.
182 *
183 * @param sourceDirs the List of the source directories
184 * @return a List of the directories that will be included in the JXR report generation
185 */
186 protected List pruneSourceDirs( List sourceDirs )
187 {
188 List pruned = new ArrayList( sourceDirs.size() );
189 for ( Iterator i = sourceDirs.iterator(); i.hasNext(); )
190 {
191 String dir = (String) i.next();
192 if ( !pruned.contains( dir ) && hasSources( new File( dir ) ) )
193 {
194 pruned.add( dir );
195 }
196 }
197 return pruned;
198 }
199
200 /**
201 * Initialize some attributes required during the report generation
202 */
203 protected void init()
204 {
205 // wanna know if Javadoc is being generated
206 // TODO: what if it is not part of the site though, and just on the command line?
207 Collection plugin = project.getReportPlugins();
208 if ( plugin != null )
209 {
210 for ( Iterator iter = plugin.iterator(); iter.hasNext(); )
211 {
212 ReportPlugin reportPlugin = (ReportPlugin) iter.next();
213 if ( "maven-javadoc-plugin".equals( reportPlugin.getArtifactId() ) )
214 {
215 break;
216 }
217 }
218 }
219 }
220
221 /**
222 * Checks whether the given directory contains Java files.
223 *
224 * @param dir the source directory
225 * @return true if the folder or one of its subfolders contains at least 1 Java file
226 */
227 private boolean hasSources( File dir )
228 {
229 boolean found = false;
230 if ( dir.exists() && dir.isDirectory() )
231 {
232 File[] files = dir.listFiles();
233 for ( int i = 0; i < files.length && !found; i++ )
234 {
235 File currentFile = files[i];
236 if ( currentFile.isFile() && currentFile.getName().endsWith( ".java" ) )
237 {
238 found = true;
239 }
240 else if ( currentFile.isDirectory() )
241 {
242 boolean hasSources = hasSources( currentFile );
243 if ( hasSources )
244 {
245 found = true;
246 }
247 }
248 }
249 }
250 return found;
251 }
252
253 /**
254 * Creates the Xref for the Java files found in the given source directory and puts
255 * them in the given destination directory.
256 *
257 * @param locale The user locale to use for the Xref generation
258 * @param destinationDirectory The output folder
259 * @param sourceDirs The source directories
260 * @throws java.io.IOException
261 * @throws org.apache.maven.jxr.JxrException
262 *
263 */
264 private void createXref( Locale locale, String destinationDirectory, List sourceDirs )
265 throws IOException, JxrException
266 {
267 JXR jxr = new JXR();
268 jxr.setDest( destinationDirectory );
269 if ( StringUtils.isEmpty( inputEncoding ) )
270 {
271 String platformEncoding = System.getProperty( "file.encoding" );
272 getLog().warn( "File encoding has not been set, using platform encoding " + platformEncoding
273 + ", i.e. build is platform dependent!" );
274 }
275 jxr.setInputEncoding( inputEncoding );
276 jxr.setLocale( locale );
277 jxr.setLog( new PluginLogAdapter( getLog() ) );
278 jxr.setOutputEncoding( getOutputEncoding() );
279 jxr.setRevision( "HEAD" );
280 jxr.setJavadocLinkDir( getJavadocLocation() );
281 // Set include/exclude patterns on the jxr instance
282 if ( excludes != null && !excludes.isEmpty() )
283 {
284 jxr.setExcludes( (String[]) excludes.toArray( new String[0] ) );
285 }
286 if ( includes != null && !includes.isEmpty() )
287 {
288 jxr.setIncludes( (String[]) includes.toArray( new String[0] ) );
289 }
290
291 jxr.xref( sourceDirs, templateDir, windowTitle, docTitle, getBottomText( project.getInceptionYear(), project
292 .getOrganization() ) );
293
294 // and finally copy the stylesheet
295 copyRequiredResources( destinationDirectory );
296 }
297
298 /**
299 * Get the bottom text to be displayed at the lower part of the generated JXR reports.
300 *
301 * @param inceptionYear the year when the project was started
302 * @param organization the organization for the project
303 * @return a String that contains the bottom text to be displayed in the lower part of the generated JXR reports
304 */
305 private String getBottomText( String inceptionYear, Organization organization )
306 {
307 int actualYear = Calendar.getInstance().get( Calendar.YEAR );
308 String year = String.valueOf( actualYear );
309
310 String bottom = StringUtils.replace( this.bottom, "{currentYear}", year );
311
312 if ( inceptionYear == null )
313 {
314 bottom = StringUtils.replace( bottom, "{inceptionYear}-", "" );
315 }
316 else
317 {
318 if ( inceptionYear.equals( year ) )
319 {
320 bottom = StringUtils.replace( bottom, "{inceptionYear}-", "" );
321 }
322 else
323 {
324 bottom = StringUtils.replace( bottom, "{inceptionYear}", inceptionYear );
325 }
326 }
327
328 if ( organization != null && StringUtils.isNotEmpty( organization.getName() ) )
329 {
330 bottom = StringUtils.replace( bottom, "{projectOrganizationName}", organization.getName() );
331 }
332 else
333 {
334 bottom = StringUtils.replace( bottom, " {projectOrganizationName}", "" );
335 }
336
337 return bottom;
338 }
339
340 /**
341 * Copy some required resources (like the stylesheet) to the
342 * given directory
343 *
344 * @param dir the directory to copy the resources to
345 */
346 private void copyRequiredResources( String dir )
347 {
348 File stylesheetFile = new File( stylesheet );
349 File destStylesheetFile = new File( dir, "stylesheet.css" );
350
351 try
352 {
353 if ( stylesheetFile.isAbsolute() )
354 {
355 FileUtils.copyFile( stylesheetFile, destStylesheetFile );
356 }
357 else
358 {
359 URL stylesheetUrl = this.getClass().getClassLoader().getResource( stylesheet );
360 FileUtils.copyURLToFile( stylesheetUrl, destStylesheetFile );
361 }
362 }
363 catch ( IOException e )
364 {
365 getLog().warn( "An error occured while copying the stylesheet to the target directory", e );
366 }
367
368 }
369
370 /**
371 * @see org.apache.maven.reporting.AbstractMavenReport#getSiteRenderer()
372 */
373 protected Renderer getSiteRenderer()
374 {
375 return siteRenderer;
376 }
377
378 /**
379 * @see org.apache.maven.reporting.AbstractMavenReport#getOutputDirectory()
380 */
381 protected String getOutputDirectory()
382 {
383 return outputDirectory.getAbsolutePath();
384 }
385
386 /**
387 * @see org.apache.maven.reporting.AbstractMavenReport#getProject()
388 */
389 public MavenProject getProject()
390 {
391 return project;
392 }
393
394 /**
395 * Returns the correct resource bundle according to the locale
396 *
397 * @param locale the locale of the user
398 * @return the bundle corresponding to the locale
399 */
400 protected ResourceBundle getBundle( Locale locale )
401 {
402 return ResourceBundle.getBundle( "jxr-report", locale, this.getClass().getClassLoader() );
403 }
404
405 /**
406 * @param sourceDirs
407 * @return true if the report could be generated
408 */
409 protected boolean canGenerateReport( List sourceDirs )
410 {
411 boolean canGenerate = !sourceDirs.isEmpty();
412
413 if ( aggregate && !project.isExecutionRoot() )
414 {
415 canGenerate = false;
416 }
417 return canGenerate;
418 }
419
420 /**
421 * @see org.apache.maven.reporting.AbstractMavenReport#executeReport(java.util.Locale)
422 */
423 protected void executeReport( Locale locale )
424 throws MavenReportException
425 {
426 List sourceDirs = constructSourceDirs();
427 if ( canGenerateReport( sourceDirs ) )
428 {
429 // init some attributes -- TODO (javadoc)
430 init();
431
432 try
433 {
434 createXref( locale, getDestinationDirectory(), sourceDirs );
435 }
436 catch ( JxrException e )
437 {
438 throw new MavenReportException( "Error while generating the HTML source code of the projet.", e );
439 }
440 catch ( IOException e )
441 {
442 throw new MavenReportException( "Error while generating the HTML source code of the projet.", e );
443 }
444 }
445 }
446
447 /**
448 * Gets the list of the source directories to be included in the JXR report generation
449 *
450 * @return a List of the source directories whose contents will be included in the JXR report generation
451 */
452 protected List constructSourceDirs()
453 {
454 List sourceDirs = new ArrayList( getSourceRoots() );
455 if ( aggregate )
456 {
457 for ( Iterator i = reactorProjects.iterator(); i.hasNext(); )
458 {
459 MavenProject project = (MavenProject) i.next();
460
461 if ( "java".equals( project.getArtifact().getArtifactHandler().getLanguage() ) )
462 {
463 sourceDirs.addAll( getSourceRoots( project ) );
464 }
465 }
466 }
467
468 sourceDirs = pruneSourceDirs( sourceDirs );
469 return sourceDirs;
470 }
471
472 /**
473 * @see org.apache.maven.reporting.AbstractMavenReport#canGenerateReport()
474 */
475 public boolean canGenerateReport()
476 {
477 return canGenerateReport( constructSourceDirs() );
478 }
479
480 /**
481 * @see org.apache.maven.reporting.AbstractMavenReport#isExternalReport()
482 */
483 public boolean isExternalReport()
484 {
485 return true;
486 }
487
488 /**
489 * @return a String that contains the location of the javadocs
490 */
491 private String getJavadocLocation()
492 throws IOException
493 {
494 String location = null;
495 if ( linkJavadoc )
496 {
497 // We don't need to do the whole translation thing like normal, because JXR does it internally.
498 // It probably shouldn't.
499 if ( getJavadocDir().exists() )
500 {
501 // XRef was already generated by manual execution of a lifecycle binding
502 location = getJavadocDir().getAbsolutePath();
503 }
504 else
505 {
506 // Not yet generated - check if the report is on its way
507
508 // Special case: using the site:stage goal
509 String stagingDirectory = System.getProperty( "stagingDirectory" );
510
511 if ( StringUtils.isNotEmpty( stagingDirectory ) )
512 {
513 String javadocDestDir = getJavadocDir().getName();
514 boolean javadocAggregate = Boolean
515 .valueOf( JxrReportUtil.getMavenJavadocPluginBasicOption( project, "aggregate", "false" ) )
516 .booleanValue();
517
518 String structureProject = JxrReportUtil.getStructure( project, false );
519
520 if ( aggregate && javadocAggregate )
521 {
522 File outputDirectory = new File( stagingDirectory, structureProject );
523 location = outputDirectory + "/" + javadocDestDir;
524 }
525 if ( !aggregate && javadocAggregate )
526 {
527 location = stagingDirectory + "/" + javadocDestDir;
528
529 String hierarchy = project.getName();
530
531 MavenProject parent = project.getParent();
532 while ( parent != null )
533 {
534 hierarchy = parent.getName();
535 parent = parent.getParent();
536 }
537 File outputDirectory = new File( stagingDirectory, hierarchy );
538 location = outputDirectory + "/" + javadocDestDir;
539 }
540 if ( aggregate && !javadocAggregate )
541 {
542 getLog().warn(
543 "The JXR plugin is configured to build an aggregated report at the root, "
544 + "not the Javadoc plugin." );
545 }
546 if ( !aggregate && !javadocAggregate )
547 {
548 location = stagingDirectory + "/" + structureProject + "/" + javadocDestDir;
549 }
550 }
551 else
552 {
553 location = getJavadocDir().getAbsolutePath();
554 }
555 }
556
557 if ( location == null )
558 {
559 getLog().warn( "Unable to locate Javadoc to link to - DISABLED" );
560 }
561 }
562
563 return location;
564 }
565
566 /**
567 * Abstract method that returns the target directory where the generated JXR reports will be put.
568 *
569 * @return a String that contains the target directory name
570 */
571 protected abstract String getDestinationDirectory();
572
573 /**
574 * Abstract method that returns the specified source directories that will be included in the JXR report generation.
575 *
576 * @return a List of the source directories
577 */
578 protected abstract List getSourceRoots();
579
580 /**
581 * Abstract method that returns the compile source directories of the specified project that will be included in the
582 * JXR report generation
583 *
584 * @param project the MavenProject where the JXR report plugin will be executed
585 * @return a List of the source directories
586 */
587 protected abstract List getSourceRoots( MavenProject project );
588
589 /**
590 * Abstract method that returns the directory of the javadoc files.
591 *
592 * @return a File for the directory of the javadocs
593 */
594 protected abstract File getJavadocDir();
595 }