View Javadoc
1   package org.apache.maven.plugins.pdf;
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.io.InputStream;
25  import java.io.Reader;
26  import java.io.StringReader;
27  import java.io.StringWriter;
28  import java.io.Writer;
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Locale;
33  import java.util.Map;
34  import java.util.Properties;
35  
36  import org.apache.commons.io.input.XmlStreamReader;
37  import org.apache.maven.artifact.Artifact;
38  import org.apache.maven.artifact.repository.ArtifactRepository;
39  import org.apache.maven.artifact.versioning.ArtifactVersion;
40  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
41  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
42  import org.apache.maven.artifact.versioning.VersionRange;
43  import org.apache.maven.doxia.Doxia;
44  import org.apache.maven.doxia.docrenderer.AbstractDocumentRenderer;
45  import org.apache.maven.doxia.docrenderer.DocumentRenderer;
46  import org.apache.maven.doxia.docrenderer.DocumentRendererContext;
47  import org.apache.maven.doxia.docrenderer.DocumentRendererException;
48  import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer;
49  import org.apache.maven.doxia.document.DocumentMeta;
50  import org.apache.maven.doxia.document.DocumentModel;
51  import org.apache.maven.doxia.document.DocumentTOCItem;
52  import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Writer;
53  import org.apache.maven.doxia.index.IndexEntry;
54  import org.apache.maven.doxia.index.IndexingSink;
55  import org.apache.maven.doxia.module.xdoc.XdocSink;
56  import org.apache.maven.doxia.parser.ParseException;
57  import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
58  import org.apache.maven.doxia.sink.Sink;
59  import org.apache.maven.doxia.sink.impl.SinkAdapter;
60  import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
61  import org.apache.maven.doxia.site.decoration.DecorationModel;
62  import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader;
63  import org.apache.maven.doxia.siterenderer.Renderer;
64  import org.apache.maven.doxia.siterenderer.RendererException;
65  import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
66  import org.apache.maven.doxia.tools.SiteTool;
67  import org.apache.maven.doxia.tools.SiteToolException;
68  import org.apache.maven.execution.MavenSession;
69  import org.apache.maven.plugin.AbstractMojo;
70  import org.apache.maven.plugin.MojoExecutionException;
71  import org.apache.maven.plugin.MojoFailureException;
72  import org.apache.maven.plugin.PluginManager;
73  import org.apache.maven.plugins.annotations.Component;
74  import org.apache.maven.plugins.annotations.Mojo;
75  import org.apache.maven.plugins.annotations.Parameter;
76  import org.apache.maven.project.MavenProject;
77  import org.apache.maven.project.MavenProjectBuilder;
78  import org.apache.maven.reporting.AbstractMavenReportRenderer;
79  import org.apache.maven.reporting.MavenReport;
80  import org.apache.maven.reporting.MavenReportException;
81  import org.apache.maven.reporting.exec.MavenReportExecution;
82  import org.apache.maven.reporting.exec.MavenReportExecutor;
83  import org.apache.maven.reporting.exec.MavenReportExecutorRequest;
84  import org.apache.maven.settings.Settings;
85  import org.codehaus.plexus.PlexusConstants;
86  import org.codehaus.plexus.PlexusContainer;
87  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
88  import org.codehaus.plexus.context.Context;
89  import org.codehaus.plexus.context.ContextException;
90  import org.codehaus.plexus.i18n.I18N;
91  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
92  import org.codehaus.plexus.util.FileUtils;
93  import org.codehaus.plexus.util.IOUtil;
94  import org.codehaus.plexus.util.PathTool;
95  import org.codehaus.plexus.util.ReaderFactory;
96  import org.codehaus.plexus.util.StringUtils;
97  import org.codehaus.plexus.util.WriterFactory;
98  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
99  
100 /**
101  * Generates a PDF document for a project documentation usually published as web site (with maven-site-plugin).
102  *
103  * @author ltheussl
104  * @version $Id$
105  */
106 @Mojo( name = "pdf", threadSafe = true )
107 public class PdfMojo
108     extends AbstractMojo implements Contextualizable
109 {
110 
111     /**
112      * The vm line separator
113      */
114     private static final String EOL = System.getProperty( "line.separator" );
115 
116     // ----------------------------------------------------------------------
117     // Mojo components
118     // ----------------------------------------------------------------------
119 
120     /**
121      * FO Document Renderer.
122      */
123     @Component( hint = "fo" )
124     private PdfRenderer foRenderer;
125 
126     /**
127      * Internationalization.
128      */
129     @Component
130     private I18N i18n;
131 
132     /**
133      * IText Document Renderer.
134      */
135     @Component( hint = "itext" )
136     private PdfRenderer itextRenderer;
137 
138     /**
139      * A comma separated list of locales supported by Maven.
140      * The first valid token will be the default Locale for this instance of the Java Virtual Machine.
141      */
142     @Parameter( property = "locales" )
143     private String locales;
144 
145     /**
146      * Site renderer.
147      */
148     @Component
149     private Renderer siteRenderer;
150 
151     /**
152      * SiteTool.
153      */
154     @Component
155     private SiteTool siteTool;
156 
157     /**
158      * The Plugin manager instance used to resolve Plugin descriptors.
159      *
160      * @since 1.1
161      */
162     @Component( role = PluginManager.class )
163     private PluginManager pluginManager;
164 
165     /**
166      * Doxia.
167      *
168      * @since 1.1
169      */
170     @Component
171     private Doxia doxia;
172 
173     /**
174      * Project builder.
175      *
176      * @since 1.1
177      */
178     @Component
179     private MavenProjectBuilder mavenProjectBuilder;
180 
181     // ----------------------------------------------------------------------
182     // Mojo Parameters
183     // ----------------------------------------------------------------------
184 
185     /**
186      * The Maven Project Object.
187      */
188     @Parameter( defaultValue = "${project}", readonly = true, required = true )
189     private MavenProject project;
190 
191     /**
192      * The Maven Settings.
193      *
194      * @since 1.1
195      */
196     @Parameter( defaultValue = "${settings}", readonly = true, required = true )
197     private Settings settings;
198 
199     /**
200      * The current build session instance.
201      *
202      * @since 1.1
203      */
204     @Parameter( defaultValue = "${session}", readonly = true, required = true )
205     private MavenSession session;
206 
207     /**
208      * Directory containing source for apt, fml and xdoc docs.
209      */
210     @Parameter( defaultValue = "${basedir}/src/site", required = true )
211     private File siteDirectory;
212 
213     /**
214      * Directory containing generated sources for apt, fml and xdoc docs.
215      *
216      * @since 1.1
217      */
218     @Parameter( defaultValue = "${project.build.directory}/generated-site", required = true )
219     private File generatedSiteDirectory;
220 
221     /**
222      * Output directory where PDF files should be created.
223      */
224     @Parameter( defaultValue = "${project.build.directory}/pdf", required = true )
225     private File outputDirectory;
226 
227     /**
228      * Working directory for working files like temp files/resources.
229      */
230     @Parameter( defaultValue = "${project.build.directory}/pdf", required = true )
231     private File workingDirectory;
232 
233     /**
234      * File that contains the DocumentModel of the PDF to generate.
235      */
236     @Parameter( defaultValue = "src/site/pdf.xml" )
237     private File docDescriptor;
238 
239     /**
240      * Identifies the framework to use for pdf generation: either "fo" (default) or "itext".
241      */
242     @Parameter( property = "implementation", defaultValue = "fo", required = true )
243     private String implementation;
244 
245     /**
246      * The local repository.
247      */
248     @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
249     private ArtifactRepository localRepository;
250 
251     /**
252      * The remote repositories where artifacts are located.
253      *
254      * @since 1.1
255      */
256     @Parameter( defaultValue = "${project.remoteArtifactRepositories}"  )
257     private List<ArtifactRepository> remoteRepositories;
258 
259     /**
260      * If <code>true</false>, aggregate all source documents in one pdf, otherwise generate one pdf for each
261      * source document.
262      */
263     @Parameter( property = "aggregate", defaultValue = "true" )
264     private boolean aggregate;
265 
266     /**
267      * The current version of this plugin.
268      */
269     @Parameter( defaultValue = "${plugin.version}", readonly = true )
270     private String pluginVersion;
271 
272     /**
273      * If <code>true</false>, generate all Maven reports defined in <code>${project.reporting}</code> and append
274      * them as a new entry in the TOC (Table Of Contents).
275      * <b>Note</b>: Including the report generation could fail the PDF generation or increase the build time.
276      *
277      * @since 1.1
278      */
279     @Parameter( property = "includeReports", defaultValue = "true" )
280     private boolean includeReports;
281 
282     /**
283      * Generate a TOC (Table Of Content) for all items defined in the &lt;toc/&gt; element from the document descriptor.
284      * <br/>
285      * Possible values are: 'none', 'start' and 'end'.
286      *
287      * @since 1.1
288      */
289     @Parameter( property = "generateTOC", defaultValue = "start" )
290     private String generateTOC;
291 
292     /**
293      * Whether to validate xml input documents.
294      * If set to true, <strong>all</strong> input documents in xml format
295      * (in particular xdoc and fml) will be validated and any error will
296      * lead to a build failure.
297      *
298      * @since 1.2
299      */
300     @Parameter( property = "validate", defaultValue = "false" )
301     private boolean validate;
302     
303     /**
304      * Reports (Maven 2).
305      *
306      * @since 1.3
307      */
308     @Parameter( defaultValue = "${reports}", required = true, readonly = true )
309     private MavenReport[] reports;
310     
311     /**
312      * Reports (Maven 3).
313      *
314      * @since 1.4
315      */
316     @Parameter( defaultValue = "${project.reporting.plugins}", readonly = true )
317     private org.apache.maven.model.ReportPlugin[] reportingPlugins;
318 
319     // ----------------------------------------------------------------------
320     // Instance fields
321     // ----------------------------------------------------------------------
322 
323     /**
324      * The current document Renderer.
325      * @see #implementation
326      */
327     private DocumentRenderer docRenderer;
328 
329     /**
330      * The default locale.
331      */
332     private Locale defaultLocale;
333 
334     /**
335      * The available locales list.
336      */
337     private List<Locale> localesList;
338 
339     /**
340      * The default decoration model.
341      */
342     private DecorationModel defaultDecorationModel;
343 
344     /**
345      * The temp Site dir to have all site and generated-site files.
346      *
347      * @since 1.1
348      */
349     private File siteDirectoryTmp;
350 
351     /**
352      * The temp Generated Site dir to have generated reports by this plugin.
353      *
354      * @since 1.1
355      */
356     private File generatedSiteDirectoryTmp;
357 
358     /**
359      * A map of generated MavenReport list using locale as key.
360      *
361      * @since 1.1
362      */
363     private Map<Locale, List<MavenReport>> generatedMavenReports;
364     
365     /**
366      * @since 1.3
367      */
368     private PlexusContainer container;
369 
370     // ----------------------------------------------------------------------
371     // Public methods
372     // ----------------------------------------------------------------------
373 
374     /** {@inheritDoc} */
375     public void execute()
376         throws MojoExecutionException, MojoFailureException
377     {
378         init();
379 
380         try
381         {
382             generatePdf();
383         }
384         catch ( IOException e )
385         {
386             debugLogGeneratedModel( getDocumentModel( Locale.ENGLISH ) );
387 
388             throw new MojoExecutionException( "Error during document generation: " + e.getMessage(), e );
389         }
390 
391         try
392         {
393             copyGeneratedPdf();
394         }
395         catch ( IOException e )
396         {
397             throw new MojoExecutionException( "Error copying generated PDF: " + e.getMessage(), e );
398         }
399     }
400     
401     /** {@inheritDoc} */
402     public void contextualize( Context context )
403         throws ContextException
404     {
405         container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
406     }
407 
408     // ----------------------------------------------------------------------
409     // Private methods
410     // ----------------------------------------------------------------------
411 
412     /**
413      * Init and validate parameters
414      */
415     private void init()
416     {
417         if ( "fo".equalsIgnoreCase( implementation ) )
418         {
419             this.docRenderer = foRenderer;
420         }
421         else if ( "itext".equalsIgnoreCase( implementation ) )
422         {
423             this.docRenderer = itextRenderer;
424         }
425         else
426         {
427             getLog().warn( "Invalid 'implementation' parameter: '" + implementation
428                     + "', using 'fo' as default." );
429 
430             this.docRenderer = foRenderer;
431         }
432 
433         if ( !( "none".equalsIgnoreCase( generateTOC )
434                 || "start".equalsIgnoreCase( generateTOC ) || "end".equalsIgnoreCase( generateTOC ) ) )
435         {
436             getLog().warn( "Invalid 'generateTOC' parameter: '" + generateTOC
437                     + "', using 'start' as default." );
438 
439             this.generateTOC = "start";
440         }
441     }
442 
443     /**
444      * Copy the generated PDF to outputDirectory.
445      *
446      * @throws MojoExecutionException if any
447      * @throws IOException if any
448      * @since 1.1
449      */
450     private void copyGeneratedPdf()
451         throws MojoExecutionException, IOException
452     {
453         boolean requireCopy = !outputDirectory.getCanonicalPath().equals( workingDirectory.getCanonicalPath() );
454 
455         String outputName = getDocumentModel( getDefaultLocale() ).getOutputName().trim();
456         if ( !outputName.endsWith( ".pdf" ) )
457         {
458             outputName = outputName.concat( ".pdf" );
459         }
460 
461         for ( final Locale locale : getAvailableLocales() )
462         {
463             File generatedPdfSource = new File( getLocaleDirectory( workingDirectory, locale ), outputName );
464 
465             if ( !generatedPdfSource.exists() )
466             {
467                 getLog().warn( "Unable to find the generated pdf: " + generatedPdfSource.getAbsolutePath() );
468                 continue;
469             }
470 
471             File generatedPdfDest = new File( getLocaleDirectory( outputDirectory, locale ), outputName );
472 
473             if ( requireCopy )
474             {
475                 FileUtils.copyFile( generatedPdfSource, generatedPdfDest );
476                 generatedPdfSource.delete();
477             }
478 
479             getLog().info( "pdf generated: " + generatedPdfDest );
480         }
481     }
482 
483     /**
484      * Generate the PDF.
485      *
486      * @throws MojoExecutionException if any
487      * @throws IOException if any
488      * @since 1.1
489      */
490     private void generatePdf()
491         throws MojoExecutionException, IOException
492     {
493         Locale.setDefault( getDefaultLocale() );
494 
495         for ( final Locale locale : getAvailableLocales() )
496         {
497             final File workingDir = getLocaleDirectory( workingDirectory, locale );
498 
499             File siteDirectoryFile = getLocaleDirectory( getSiteDirectoryTmp(), locale );
500 
501             copyResources( locale );
502 
503             generateMavenReports( locale );
504 
505             DocumentRendererContext context = new DocumentRendererContext();
506             context.put( "project", project );
507             context.put( "settings", settings );
508             context.put( "PathTool", new PathTool() );
509             context.put( "FileUtils", new FileUtils() );
510             context.put( "StringUtils", new StringUtils() );
511             context.put( "i18n", i18n );
512             context.put( "generateTOC", generateTOC );
513             context.put( "validate", validate );
514 
515             // Put any of the properties in directly into the Velocity context
516             for ( Map.Entry<Object, Object> entry : project.getProperties().entrySet() )
517             {
518                 context.put( (String) entry.getKey(), entry.getValue() );
519             }
520             
521             final DocumentModel model = aggregate ? getDocumentModel( locale ) : null;
522 
523             try
524             {
525                 // TODO use interface see DOXIASITETOOLS-30
526                 ( (AbstractDocumentRenderer) docRenderer ).render( siteDirectoryFile, workingDir, model, context );
527             }
528             catch ( DocumentRendererException e )
529             {
530                 throw new MojoExecutionException( "Error during document generation: " + e.getMessage(), e );
531             }
532         }
533     }
534 
535     /**
536      * @return the default tmpSiteDirectory.
537      * @throws IOException if any
538      * @since 1.1
539      */
540     private File getSiteDirectoryTmp()
541         throws IOException
542     {
543         if ( this.siteDirectoryTmp == null )
544         {
545             final File tmpSiteDir = new File( workingDirectory, "site.tmp" );
546             prepareTempSiteDirectory( tmpSiteDir );
547 
548             this.siteDirectoryTmp = tmpSiteDir;
549         }
550 
551         return this.siteDirectoryTmp;
552     }
553 
554     /**
555      * @return the default tmpGeneratedSiteDirectory when report will be created.
556      * @since 1.1
557      */
558     private File getGeneratedSiteDirectoryTmp()
559     {
560         if ( this.generatedSiteDirectoryTmp == null )
561         {
562             this.generatedSiteDirectoryTmp = new File( workingDirectory, "generated-site.tmp" );
563         }
564 
565         return this.generatedSiteDirectoryTmp;
566     }
567 
568     /**
569      * Copy all site and generated-site files in the tmpSiteDirectory.
570      * <br/>
571      * <b>Note</b>: ignore copying of <code>generated-site</code> files if they already exist in the
572      * <code>site</code> dir.
573      *
574      * @param tmpSiteDir not null
575      * @throws IOException if any
576      * @since 1.1
577      */
578     private void prepareTempSiteDirectory( final File tmpSiteDir )
579         throws IOException
580     {
581         // safety
582         tmpSiteDir.mkdirs();
583 
584         // copy site
585         if ( siteDirectory.exists() )
586         {
587             FileUtils.copyDirectoryStructure( siteDirectory, tmpSiteDir );
588         }
589 
590         // Remove SCM files
591         List<String> files =
592             FileUtils.getFileAndDirectoryNames( tmpSiteDir, FileUtils.getDefaultExcludesAsString(), null, true,
593                                                 true, true, true );
594         for ( final String fileName : files )
595         {
596             final File file = new File( fileName );
597 
598             if ( file.isDirectory() )
599             {
600                 FileUtils.deleteDirectory( file );
601             }
602             else
603             {
604                 file.delete();
605             }
606         }
607 
608         copySiteDir( generatedSiteDirectory, tmpSiteDir );
609     }
610 
611     /**
612      * Copy the from site dir to the to dir.
613      *
614      * @param from not null
615      * @param to not null
616      * @throws IOException if any
617      * @since 1.1
618      */
619     private void copySiteDir( final File from, final File to )
620         throws IOException
621     {
622         if ( from == null || !from.exists() )
623         {
624             return;
625         }
626 
627         // copy generated-site
628         for ( final Locale locale : getAvailableLocales() )
629         {
630             String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() );
631             List<String> siteFiles = FileUtils.getFileNames( siteDirectory, "**/*", excludes, false );
632             File siteDirectoryLocale = new File( siteDirectory, locale.getLanguage() );
633             if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) && siteDirectoryLocale.exists() )
634             {
635                 siteFiles = FileUtils.getFileNames( siteDirectoryLocale, "**/*", excludes, false );
636             }
637 
638             List<String> generatedSiteFiles = FileUtils.getFileNames( from, "**/*", excludes, false );
639             File fromLocale = new File( from, locale.getLanguage() );
640             if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) && fromLocale.exists() )
641             {
642                 generatedSiteFiles = FileUtils.getFileNames( fromLocale, "**/*", excludes, false );
643             }
644 
645             for ( final String generatedSiteFile : generatedSiteFiles )
646             {
647                 if ( siteFiles.contains( generatedSiteFile ) )
648                 {
649                     getLog().warn( "Generated-site already contains a file in site: " + generatedSiteFile
650                                        + ". Ignoring copying it!" );
651                     continue;
652                 }
653 
654                 if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) )
655                 {
656                     if ( fromLocale.exists() )
657                     {
658                         File in = new File( fromLocale, generatedSiteFile );
659                         File out = new File( new File( to, locale.getLanguage() ), generatedSiteFile );
660                         out.getParentFile().mkdirs();
661                         FileUtils.copyFile( in, out );
662                     }
663                 }
664                 else
665                 {
666                     File in = new File( from, generatedSiteFile );
667                     File out = new File( to, generatedSiteFile );
668                     out.getParentFile().mkdirs();
669                     FileUtils.copyFile( in, out );
670                 }
671             }
672         }
673     }
674 
675     /**
676      * Constructs a DocumentModel for the current project. The model is either read from
677      * a descriptor file, if it exists, or constructed from information in the pom and site.xml.
678      *
679      * @param locale not null
680      * @return DocumentModel.
681      * @throws MojoExecutionException if any
682      * @see #appendGeneratedReports(DocumentModel, Locale)
683      */
684     private DocumentModel getDocumentModel( Locale locale )
685         throws MojoExecutionException
686     {
687         if ( docDescriptor.exists() )
688         {
689             DocumentModel doc = getDocumentModelFromDescriptor( locale );
690             // TODO: descriptor model should get merged into default model, see MODELLO-63
691 
692             appendGeneratedReports( doc, locale );
693 
694             return doc;
695         }
696 
697         DocumentModel model = new DocumentModelBuilder( project, getDefaultDecorationModel() ).getDocumentModel();
698 
699         model.getMeta().setGenerator( getDefaultGenerator() );
700         model.getMeta().setLanguage( locale.getLanguage() );
701         model.getCover().setCoverType( i18n.getString( "pdf-plugin", getDefaultLocale(), "toc.type" ) );
702         model.getToc().setName( i18n.getString( "pdf-plugin", getDefaultLocale(), "toc.title" ) );
703 
704         appendGeneratedReports( model, locale );
705 
706         debugLogGeneratedModel( model );
707 
708         return model;
709     }
710 
711     /**
712      * Read a DocumentModel from a file.
713      *
714      * @param locale used to set the language.
715      * @return the DocumentModel read from the configured document descriptor.
716      * @throws org.apache.maven.plugin.MojoExecutionException if the model could not be read.
717      */
718     private DocumentModel getDocumentModelFromDescriptor( Locale locale )
719         throws MojoExecutionException
720     {
721         DocumentModel model;
722 
723         try
724         {
725             model = new DocumentDescriptorReader( project, getLog(),
726                                                   locale ).readAndFilterDocumentDescriptor( docDescriptor );
727         }
728         catch ( XmlPullParserException ex )
729         {
730             throw new MojoExecutionException( "Error reading DocumentDescriptor!", ex );
731         }
732         catch ( IOException io )
733         {
734             throw new MojoExecutionException( "Error opening DocumentDescriptor!", io );
735         }
736 
737         if ( model.getMeta() == null )
738         {
739             model.setMeta( new DocumentMeta() );
740         }
741 
742         if ( StringUtils.isEmpty( model.getMeta().getLanguage() ) )
743         {
744             model.getMeta().setLanguage( locale.getLanguage() );
745         }
746 
747         if ( StringUtils.isEmpty( model.getMeta().getGenerator() ) )
748         {
749             model.getMeta().setGenerator( getDefaultGenerator() );
750         }
751 
752         return model;
753     }
754 
755     /**
756      * Return the directory for a given Locale and the current default Locale.
757      *
758      * @param basedir the base directory
759      * @param locale a Locale.
760      * @return File.
761      */
762     private File getLocaleDirectory( File basedir, Locale locale )
763     {
764         if ( locale.getLanguage().equals( getDefaultLocale().getLanguage() ) )
765         {
766             return basedir;
767         }
768 
769         return new File( basedir, locale.getLanguage() );
770     }
771 
772     /**
773      * @return the default locale from <code>siteTool</code>.
774      * @see #getAvailableLocales()
775      */
776     private Locale getDefaultLocale()
777     {
778         if ( this.defaultLocale == null )
779         {
780             this.defaultLocale = getAvailableLocales().get( 0 );
781         }
782 
783         return this.defaultLocale;
784     }
785 
786     /**
787      * @return the available locales from <code>siteTool</code>.
788      */
789     private List<Locale> getAvailableLocales()
790     {
791         if ( this.localesList == null )
792         {
793             this.localesList = siteTool.getSiteLocales( locales );
794         }
795 
796         return this.localesList;
797     }
798 
799     /**
800      * @return the DecorationModel instance from <code>site.xml</code>
801      * @throws MojoExecutionException if any
802      */
803     private DecorationModel getDefaultDecorationModel()
804         throws MojoExecutionException
805     {
806         if ( this.defaultDecorationModel == null )
807         {
808             final Locale locale = getDefaultLocale();
809 
810             final File descriptorFile = siteTool.getSiteDescriptor( siteDirectory, locale );
811             DecorationModel decoration = null;
812 
813             if ( descriptorFile.exists() )
814             {
815                 XmlStreamReader reader = null;
816                 try
817                 {
818                     reader = new XmlStreamReader( descriptorFile );
819 
820                     String siteDescriptorContent = IOUtil.toString( reader );
821 
822                     reader.close();
823                     reader = null;
824 
825                     siteDescriptorContent =
826                         siteTool.getInterpolatedSiteDescriptorContent( new HashMap<String, String>( 2 ), project,
827                                                                        siteDescriptorContent );
828 
829                     decoration = new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) );
830                 }
831                 catch ( XmlPullParserException e )
832                 {
833                     throw new MojoExecutionException( "Error parsing site descriptor", e );
834                 }
835                 catch ( IOException e )
836                 {
837                     throw new MojoExecutionException( "Error reading site descriptor", e );
838                 }
839                 catch ( SiteToolException e )
840                 {
841                     throw new MojoExecutionException( "Error when interpoling site descriptor", e );
842                 }
843                 finally
844                 {
845                     IOUtil.close( reader );
846                 }
847             }
848 
849             this.defaultDecorationModel = decoration;
850         }
851 
852         return this.defaultDecorationModel;
853     }
854 
855     /**
856      * Parse the decoration model to find the skin artifact and copy its resources to the output dir.
857      *
858      * @param locale not null
859      * @throws MojoExecutionException if any
860      * @see #getDefaultDecorationModel()
861      */
862     private void copyResources( Locale locale )
863         throws MojoExecutionException
864     {
865         final DecorationModel decorationModel = getDefaultDecorationModel();
866         if ( decorationModel == null )
867         {
868             return;
869         }
870 
871         Artifact skinArtifact;
872         try
873         {
874             skinArtifact =
875                 siteTool.getSkinArtifactFromRepository( localRepository, project.getRemoteArtifactRepositories(),
876                                                         decorationModel );
877         }
878         catch ( SiteToolException e )
879         {
880             throw new MojoExecutionException( "SiteToolException: " + e.getMessage(), e );
881         }
882 
883         if ( skinArtifact == null )
884         {
885             return;
886         }
887 
888         if ( getLog().isDebugEnabled() )
889         {
890             getLog().debug( "Copy resources from skin artifact: '" + skinArtifact.getId() + "'..." );
891         }
892 
893         try
894         {
895             final SiteRenderingContext context =
896                 siteRenderer.createContextForSkin( skinArtifact, new HashMap<String, Object>( 2 ), decorationModel,
897                                                    project.getName(), locale );
898             context.addSiteDirectory( new File( siteDirectory, locale.getLanguage() ) );
899 
900             siteRenderer.copyResources( context, workingDirectory );
901         }
902         catch ( IOException e )
903         {
904             throw new MojoExecutionException( "IOException: " + e.getMessage(), e );
905         }
906         catch ( RendererException e )
907         {
908             throw new MojoExecutionException( "RendererException: " + e.getMessage(), e );
909         }
910     }
911 
912     /**
913      * Construct a default producer.
914      *
915      * @return A String in the form <code>Maven PDF Plugin v. 1.1.1, 'fo' implementation</code>.
916      */
917     private String getDefaultGenerator()
918     {
919         return "Maven PDF Plugin v. " + pluginVersion + ", '" + implementation + "' implementation.";
920     }
921 
922     /**
923      * Write the auto-generated model to disc.
924      *
925      * @param docModel the model to write.
926      */
927     private void debugLogGeneratedModel( final DocumentModel docModel )
928     {
929         if ( getLog().isDebugEnabled() && project != null )
930         {
931             final File outputDir = new File( project.getBuild().getDirectory(), "pdf" );
932 
933             if ( !outputDir.exists() )
934             {
935                 outputDir.mkdirs();
936             }
937 
938             final File doc = FileUtils.createTempFile( "pdf", ".xml", outputDir );
939             final DocumentXpp3Writer xpp3 = new DocumentXpp3Writer();
940 
941             Writer w = null;
942             try
943             {
944                 w = WriterFactory.newXmlWriter( doc );
945                 xpp3.write( w, docModel );
946                 w.close();
947                 w = null;
948                 getLog().debug( "Generated a default document model: " + doc.getAbsolutePath() );
949             }
950             catch ( IOException e )
951             {
952                 getLog().error( "Failed to write document model: " + e.getMessage() );
953                 getLog().debug( e );
954             }
955             finally
956             {
957                 IOUtil.close( w );
958             }
959         }
960     }
961 
962     /**
963      * Generate all Maven reports defined in <code>${project.reporting}</code> part
964      * only if <code>generateReports</code> is enabled.
965      *
966      * @param locale not null
967      * @throws MojoExecutionException if any
968      * @throws IOException if any
969      * @since 1.1
970      */
971     private void generateMavenReports( Locale locale )
972         throws MojoExecutionException, IOException
973     {
974         if ( !includeReports )
975         {
976             getLog().info( "Skipped report generation." );
977             return;
978         }
979 
980         if ( project.getReporting() == null )
981         {
982             getLog().info( "No report was specified." );
983             return;
984         }
985 
986         List<MavenReportExecution> reportExecutions = getReports();
987         for ( MavenReportExecution reportExecution : reportExecutions )
988         {
989             generateMavenReport( reportExecution, locale );
990         }
991 
992         // copy generated site
993         copySiteDir( getGeneratedSiteDirectoryTmp(), getSiteDirectoryTmp() );
994         copySiteDir( generatedSiteDirectory, getSiteDirectoryTmp() );
995     }
996 
997     /**
998      * Generate the given Maven report only if it is not an external report and the report could be generated.
999      *
1000      * @param reportExecution not null
1001      * @param locale not null
1002      * @throws IOException if any
1003      * @throws MojoExecutionException if any
1004      * @since 1.1
1005      */
1006     private void generateMavenReport( MavenReportExecution reportExecution, Locale locale )
1007         throws IOException, MojoExecutionException
1008     {
1009         MavenReport report = reportExecution.getMavenReport();
1010 
1011         String localReportName = report.getName( locale );
1012 
1013         if ( !reportExecution.canGenerateReport() )
1014         {
1015             getLog().info( "Skipped \"" + localReportName + "\" report." );
1016             getLog().debug( "canGenerateReport() was false." );
1017 
1018             return;
1019         }
1020 
1021         if ( report.isExternalReport() )
1022         {
1023             getLog().info( "Skipped external \"" + localReportName + "\" report (not supported by pdf plugin)." );
1024             getLog().debug( "isExternalReport() was false." );
1025 
1026             return;
1027         }
1028 
1029         for ( final MavenReport generatedReport : getGeneratedMavenReports( locale ) )
1030         {
1031             if ( report.getName( locale ).equals( generatedReport.getName( locale ) ) )
1032             {
1033                 if ( getLog().isDebugEnabled() )
1034                 {
1035                     getLog().debug( report.getName( locale ) + " was already generated." );
1036                 }
1037                 return;
1038             }
1039         }
1040 
1041         File outDir = new File( getGeneratedSiteDirectoryTmp(), "xdoc" );
1042         if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) )
1043         {
1044             outDir = new File( new File( getGeneratedSiteDirectoryTmp(), locale.getLanguage() ), "xdoc" );
1045         }
1046         outDir.mkdirs();
1047 
1048         File generatedReport = new File( outDir, report.getOutputName() + ".xml" );
1049 
1050         String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() );
1051         List<String> files =
1052             FileUtils.getFileNames( siteDirectory, "*/" + report.getOutputName() + ".*", excludes, false );
1053         if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) )
1054         {
1055             files =
1056                 FileUtils.getFileNames( new File( siteDirectory, locale.getLanguage() ), "*/"
1057                     + report.getOutputName() + ".*", excludes, false );
1058         }
1059 
1060         if ( files.size() != 0 )
1061         {
1062             String displayLanguage = locale.getDisplayLanguage( Locale.ENGLISH );
1063 
1064             if ( getLog().isInfoEnabled() )
1065             {
1066                 getLog().info(
1067                                "Skipped \"" + report.getName( locale ) + "\" report, file \""
1068                                    + report.getOutputName() + "\" already exists for the " + displayLanguage
1069                                    + " version." );
1070             }
1071 
1072             return;
1073         }
1074 
1075         if ( getLog().isInfoEnabled() )
1076         {
1077             getLog().info( "Generating \"" + localReportName + "\" report." );
1078         }
1079 
1080         StringWriter sw = new StringWriter();
1081 
1082         PdfXdocSink sink = null;
1083         try
1084         {
1085             sink = new PdfXdocSink( sw );
1086             renderReportToSink( reportExecution, locale, sink );
1087         }
1088         catch ( MavenReportException e )
1089         {
1090             String goal = reportExecution.getPlugin().getArtifactId() + ':' + reportExecution.getPlugin().getVersion()
1091                 + ':' + reportExecution.getGoal();
1092             throw new MojoExecutionException( "Error generating " + goal + " report", e );
1093         }
1094         finally
1095         {
1096             if ( sink != null )
1097             {
1098                 sink.close();
1099             }
1100         }
1101 
1102         if ( getLog().isDebugEnabled() )
1103         {
1104             getLog().debug( "Writing generated xdoc to " + generatedReport );
1105         }
1106         writeGeneratedReport( sw.toString(), generatedReport );
1107 
1108         // keep generated report xdoc only if it is valid
1109         if ( isValidGeneratedReportXdoc( reportExecution.getPlugin().getId() + ':' + reportExecution.getGoal(),
1110                                          generatedReport, localReportName ) )
1111         {
1112             getGeneratedMavenReports( locale ).add( report );
1113         }
1114     }
1115 
1116     /**
1117      * see org.apache.maven.plugins.site.render.ReportDocumentRenderer#renderDocument(...)
1118      *
1119      * @param reportExec
1120      * @param locale
1121      * @param sink
1122      * @throws MavenReportException
1123      */
1124     private void renderReportToSink( MavenReportExecution reportExec, Locale locale, PdfXdocSink sink )
1125         throws MavenReportException
1126     {
1127         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
1128         try
1129         {
1130             if ( reportExec.getClassLoader() != null )
1131             {
1132                 Thread.currentThread().setContextClassLoader( reportExec.getClassLoader() );
1133             }
1134     
1135             MavenReport report = reportExec.getMavenReport();
1136     
1137             /*if ( report instanceof MavenMultiPageReport )
1138             {
1139                 // extended multi-page API
1140                 ( (MavenMultiPageReport) report ).generate( mainSink, multiPageSinkFactory, locale );
1141             }
1142             else if ( generateMultiPage( locale, multiPageSinkFactory, mainSink ) )
1143             {
1144                 // extended multi-page API for Maven 2.2, only accessible by reflection API
1145             }
1146             else
1147             {*/
1148             // old single-page-only API
1149             report.generate( sink, locale );
1150             //}
1151         }
1152         finally
1153         {
1154             if ( reportExec.getClassLoader() != null )
1155             {
1156                 Thread.currentThread().setContextClassLoader( originalClassLoader );
1157             }
1158         }
1159     }
1160 
1161     /**
1162      * @param locale not null
1163      * @return the generated reports
1164      * @since 1.1
1165      */
1166     private List<MavenReport> getGeneratedMavenReports( Locale locale )
1167     {
1168         if ( this.generatedMavenReports == null )
1169         {
1170             this.generatedMavenReports = new HashMap<Locale, List<MavenReport>>( 2 );
1171         }
1172 
1173         if ( this.generatedMavenReports.get( locale ) == null )
1174         {
1175             this.generatedMavenReports.put( locale, new ArrayList<MavenReport>( 2 ) );
1176         }
1177 
1178         return this.generatedMavenReports.get( locale );
1179     }
1180 
1181     /**
1182      * Append generated reports to the toc only if <code>generateReports</code> is enabled, for instance:
1183      * <pre>
1184      * &lt;item name="Project Reports" ref="/project-info"&gt;
1185      * &nbsp;&nbsp;&lt;item name="Project License" ref="/license" /&gt;
1186      * &nbsp;&nbsp;&lt;item name="Project Team" ref="/team-list" /&gt;
1187      * &nbsp;&nbsp;&lt;item name="Continuous Integration" ref="/integration" /&gt;
1188      * &nbsp;&nbsp;...
1189      * &lt;/item&gt;
1190      * </pre>
1191      *
1192      * @param model not null
1193      * @param locale not null
1194      * @see #generateMavenReports(Locale)
1195      * @since 1.1
1196      */
1197     private void appendGeneratedReports( DocumentModel model, Locale locale )
1198     {
1199         if ( !includeReports )
1200         {
1201             return;
1202         }
1203         if ( getGeneratedMavenReports( locale ).isEmpty() )
1204         {
1205             return;
1206         }
1207 
1208         final DocumentTOCItem documentTOCItem = new DocumentTOCItem();
1209         documentTOCItem.setName( i18n.getString( "pdf-plugin", locale, "toc.project-info.item" ) );
1210         documentTOCItem.setRef( "/project-info" ); // see #generateMavenReports(Locale)
1211 
1212         List<String> addedRef = new ArrayList<String>( 4 );
1213 
1214         List<DocumentTOCItem> items = new ArrayList<DocumentTOCItem>( 4 );
1215 
1216         // append generated report defined as MavenReport
1217         for ( final MavenReport report : getGeneratedMavenReports( locale ) )
1218         {
1219             final DocumentTOCItem reportItem = new DocumentTOCItem();
1220             reportItem.setName( report.getName( locale ) );
1221             reportItem.setRef( "/" + report.getOutputName() );
1222 
1223             items.add( reportItem );
1224 
1225             addedRef.add( report.getOutputName() );
1226         }
1227 
1228         // append all generated reports from generated-site
1229         try
1230         {
1231             if ( generatedSiteDirectory.exists() )
1232             {
1233                 String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() );
1234                 List<String> generatedDirs = FileUtils.getDirectoryNames( generatedSiteDirectory, "*", excludes,
1235                                                                           true );
1236                 if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) )
1237                 {
1238                     generatedDirs =
1239                         FileUtils.getFileNames( new File( generatedSiteDirectory, locale.getLanguage() ), "*",
1240                                                 excludes, true );
1241                 }
1242 
1243                 for ( final String generatedDir : generatedDirs )
1244                 {
1245                     List<String> generatedFiles =
1246                         FileUtils.getFileNames( new File( generatedDir ), "**.*", excludes, false );
1247 
1248                     for ( final String generatedFile : generatedFiles )
1249                     {
1250                         final String ref = generatedFile.substring( 0, generatedFile.lastIndexOf( '.' ) );
1251 
1252                         if ( !addedRef.contains( ref ) )
1253                         {
1254                             final String title =
1255                                 getGeneratedDocumentTitle( new File( generatedDir, generatedFile ) );
1256 
1257                             if ( title != null )
1258                             {
1259                                 final DocumentTOCItem reportItem = new DocumentTOCItem();
1260                                 reportItem.setName( title );
1261                                 reportItem.setRef( "/" + ref );
1262 
1263                                 items.add( reportItem );
1264                             }
1265                         }
1266                     }
1267                 }
1268             }
1269         }
1270         catch ( IOException e )
1271         {
1272             getLog().error( "IOException: " + e.getMessage() );
1273             getLog().debug( e );
1274         }
1275 
1276         // append to Toc
1277         documentTOCItem.setItems( items );
1278         model.getToc().addItem( documentTOCItem );
1279     }
1280 
1281     /**
1282      * Parse a generated Doxia file and returns its title.
1283      *
1284      * @param f not null
1285      * @return the xdoc file title or null if an error occurs.
1286      * @throws IOException if any
1287      * @since 1.1
1288      */
1289     private String getGeneratedDocumentTitle( final File f )
1290         throws IOException
1291     {
1292         final IndexEntry entry = new IndexEntry( "index" );
1293         final IndexingSink titleSink = new IndexingSink( entry );
1294 
1295         Reader reader = null;
1296         try
1297         {
1298             reader = ReaderFactory.newXmlReader( f );
1299 
1300             doxia.parse( reader, f.getParentFile().getName(), titleSink );
1301 
1302             reader.close();
1303             reader = null;
1304         }
1305         catch ( ParseException e )
1306         {
1307             getLog().error( "ParseException: " + e.getMessage() );
1308             getLog().debug( e );
1309             return null;
1310         }
1311         catch ( ParserNotFoundException e )
1312         {
1313             getLog().error( "ParserNotFoundException: " + e.getMessage() );
1314             getLog().debug( e );
1315             return null;
1316         }
1317         finally
1318         {
1319             IOUtil.close( reader );
1320         }
1321 
1322         return titleSink.getTitle();
1323     }
1324 
1325     /**
1326      * Parsing the generated report to see if it is correct or not. Log the error for the user.
1327      *
1328      * @param fullGoal not null
1329      * @param generatedReport not null
1330      * @param localReportName not null
1331      * @return <code>true</code> if Doxia is able to parse the generated report, <code>false</code> otherwise.
1332      * @since 1.1
1333      */
1334     private boolean isValidGeneratedReportXdoc( String fullGoal, File generatedReport, String localReportName )
1335     {
1336         SinkAdapter sinkAdapter = new SinkAdapter();
1337         Reader reader = null;
1338         try
1339         {
1340             reader = ReaderFactory.newXmlReader( generatedReport );
1341 
1342             doxia.parse( reader, generatedReport.getParentFile().getName(), sinkAdapter );
1343 
1344             reader.close();
1345             reader = null;
1346         }
1347         catch ( ParseException e )
1348         {
1349             StringBuilder sb = new StringBuilder( 1024 );
1350 
1351             sb.append( EOL );
1352             sb.append( "Error when parsing the generated report xdoc file: " );
1353             sb.append( generatedReport.getAbsolutePath() );
1354             sb.append( EOL );
1355             sb.append( e.getMessage() );
1356             sb.append( EOL );
1357 
1358             sb.append( "You could:" ).append( EOL );
1359             sb.append( "  * exclude all reports using -DincludeReports=false" ).append( EOL );
1360             sb.append( "  * remove the " );
1361             sb.append( fullGoal );
1362             sb.append( " from the <reporting/> part. To not affect the site generation, " );
1363             sb.append( "you could create a PDF profile." );
1364 
1365             sb.append( EOL ).append( "Ignoring the \"" ).append( localReportName )
1366                     .append( "\" report in the PDF." ).append( EOL );
1367 
1368             getLog().error( sb.toString() );
1369             getLog().debug( e );
1370 
1371             return false;
1372         }
1373         catch ( ParserNotFoundException e )
1374         {
1375             getLog().error( "ParserNotFoundException: " + e.getMessage() );
1376             getLog().debug( e );
1377 
1378             return false;
1379         }
1380         catch ( IOException e )
1381         {
1382             getLog().error( "IOException: " + e.getMessage() );
1383             getLog().debug( e );
1384 
1385             return false;
1386         }
1387         finally
1388         {
1389             IOUtil.close( reader );
1390         }
1391 
1392         return true;
1393     }
1394 
1395     protected List<MavenReportExecution> getReports()
1396         throws MojoExecutionException
1397     {
1398         if ( !isMaven3OrMore() )
1399         {
1400             getLog().error( "Report generation is not supported with Maven <= 2.x" );
1401         }
1402 
1403         MavenReportExecutorRequest mavenReportExecutorRequest = new MavenReportExecutorRequest();
1404         mavenReportExecutorRequest.setLocalRepository( localRepository );
1405         mavenReportExecutorRequest.setMavenSession( session );
1406         mavenReportExecutorRequest.setProject( project );
1407         mavenReportExecutorRequest.setReportPlugins( reportingPlugins );
1408 
1409         MavenReportExecutor mavenReportExecutor;
1410         try
1411         {
1412             mavenReportExecutor = (MavenReportExecutor) container.lookup( MavenReportExecutor.class.getName() );
1413         }
1414         catch ( ComponentLookupException e )
1415         {
1416             throw new MojoExecutionException( "could not get MavenReportExecutor component", e );
1417         }
1418         return mavenReportExecutor.buildMavenReports( mavenReportExecutorRequest );
1419     }
1420 
1421     /**
1422      * Check the current Maven version to see if it's Maven 3.0 or newer.
1423      */
1424     protected static boolean isMaven3OrMore()
1425     {
1426         try
1427         {
1428             ArtifactVersion mavenVersion = new DefaultArtifactVersion( getMavenVersion() );
1429             return VersionRange.createFromVersionSpec( "[3.0,)" ).containsVersion( mavenVersion );
1430         }
1431         catch ( InvalidVersionSpecificationException e )
1432         {
1433             return false;
1434         }
1435 //        return new ComparableVersion( getMavenVersion() ).compareTo( new ComparableVersion( "3.0" ) ) >= 0;
1436     }
1437 
1438     protected static String getMavenVersion()
1439     {
1440         // This relies on the fact that MavenProject is the in core classloader
1441         // and that the core classloader is for the maven-core artifact
1442         // and that should have a pom.properties file
1443         // if this ever changes, we will have to revisit this code.
1444         final Properties properties = new Properties();
1445 
1446         InputStream in = null;
1447         try
1448         {
1449             in = MavenProject.class.getClassLoader().getResourceAsStream( "META-INF/maven/org.apache.maven/maven-core/"
1450                                                                               + "pom.properties" );
1451 
1452             properties.load( in );
1453             in.close();
1454             in = null;
1455         }
1456         catch ( IOException ioe )
1457         {
1458             return "";
1459         }
1460         finally
1461         {
1462             IOUtil.close( in );
1463         }
1464 
1465         return properties.getProperty( "version" ).trim();
1466     }
1467 
1468     // ----------------------------------------------------------------------
1469     // static methods
1470     // ----------------------------------------------------------------------
1471 
1472     /**
1473      * Write the given content to the given file.
1474      * <br/>
1475      * <b>Note</b>: try also to fix the content due to some issues in
1476      * {@link org.apache.maven.reporting.AbstractMavenReport}.
1477      *
1478      * @param content the given content
1479      * @param toFile the report file
1480      * @throws IOException if any
1481      * @since 1.1
1482      */
1483     private static void writeGeneratedReport( String content, File toFile )
1484         throws IOException
1485     {
1486         if ( StringUtils.isEmpty( content ) )
1487         {
1488             return;
1489         }
1490 
1491         Writer writer = null;
1492         try
1493         {
1494             writer = WriterFactory.newXmlWriter( toFile );
1495             // see PdfSink#table()
1496             writer.write( StringUtils.replace( content, "<table><table", "<table" ) );
1497 
1498             writer.close();
1499             writer = null;
1500         }
1501         finally
1502         {
1503             IOUtil.close( writer );
1504         }
1505     }
1506 
1507     /**
1508      * @param locales the list of locales dir to exclude
1509      * @param defaultLocale the default locale.
1510      * @return the comma separated list of default excludes and locales dir.
1511      * @see FileUtils#getDefaultExcludesAsString()
1512      * @since 1.1
1513      */
1514     private static String getDefaultExcludesWithLocales( List<Locale> locales, Locale defaultLocale )
1515     {
1516         String excludesLocales = FileUtils.getDefaultExcludesAsString();
1517         for ( final Locale locale : locales )
1518         {
1519             if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) )
1520             {
1521                 excludesLocales = excludesLocales + ",**/" + locale.getLanguage() + "/*";
1522             }
1523         }
1524 
1525         return excludesLocales;
1526     }
1527 
1528     // ----------------------------------------------------------------------
1529     // Inner class
1530     // ----------------------------------------------------------------------
1531 
1532     /**
1533      * A sink to generate a Maven report as xdoc with some known workarounds.
1534      *
1535      * @since 1.1
1536      */
1537     private static class PdfXdocSink
1538         extends XdocSink
1539         implements org.codehaus.doxia.sink.Sink
1540     {
1541         protected PdfXdocSink( Writer writer )
1542         {
1543             super( writer );
1544         }
1545 
1546         /** {@inheritDoc} */
1547         public void text( String text )
1548         {
1549             // workaround to fix quotes introduced with MPIR-59 (then removed in MPIR-136)
1550             super.text( StringUtils.replace( text, "\u0092", "'" ) );
1551         }
1552 
1553         public void tableRow()
1554         {
1555             // To be backward compatible: TODO add to XdocSink
1556             if ( !this.tableRows )
1557             {
1558                 tableRows( null, false );
1559             }
1560             super.tableRow( null );
1561         }
1562     }
1563 
1564     /**
1565      * Renderer Maven report similar to org.apache.maven.plugins.site.CategorySummaryDocumentRenderer
1566      *
1567      * @since 1.1
1568      */
1569     private static class ProjectInfoRenderer
1570         extends AbstractMavenReportRenderer
1571     {
1572         private final List<MavenReport> generatedReports;
1573 
1574         private final I18N i18n;
1575 
1576         private final Locale locale;
1577 
1578         ProjectInfoRenderer( Sink sink, List<MavenReport> generatedReports, I18N i18n, Locale locale )
1579         {
1580             super( sink );
1581 
1582             this.generatedReports = generatedReports;
1583             this.i18n = i18n;
1584             this.locale = locale;
1585         }
1586 
1587         /** {@inheritDoc} */
1588         public String getTitle()
1589         {
1590             return i18n.getString( "pdf-plugin", locale, "report.project-info.title" );
1591         }
1592 
1593         /** {@inheritDoc} */
1594         public void renderBody()
1595         {
1596             sink.section1();
1597             sink.sectionTitle1();
1598             sink.text( i18n.getString( "pdf-plugin", locale, "report.project-info.title" ) );
1599             sink.sectionTitle1_();
1600 
1601             sink.paragraph();
1602             sink.text( i18n.getString( "pdf-plugin", locale, "report.project-info.description1" ) + " " );
1603             sink.link( "http://maven.apache.org" );
1604             sink.text( "Maven" );
1605             sink.link_();
1606             sink.text( " " + i18n.getString( "pdf-plugin", locale, "report.project-info.description2" ) );
1607             sink.paragraph_();
1608 
1609             sink.section2();
1610             sink.sectionTitle2();
1611             sink.text( i18n.getString( "pdf-plugin", locale, "report.project-info.sectionTitle" ) );
1612             sink.sectionTitle2_();
1613 
1614             sink.table();
1615 
1616             sink.tableRows( new int[] { Sink.JUSTIFY_LEFT, Sink.JUSTIFY_LEFT }, false );
1617 
1618             String name = i18n.getString( "pdf-plugin", locale, "report.project-info.column.document" );
1619             String description = i18n.getString( "pdf-plugin", locale, "report.project-info.column.description" );
1620 
1621             sink.tableRow();
1622 
1623             sink.tableHeaderCell( SinkEventAttributeSet.CENTER );
1624 
1625             sink.text( name );
1626 
1627             sink.tableHeaderCell_();
1628 
1629             sink.tableHeaderCell( SinkEventAttributeSet.CENTER );
1630 
1631             sink.text( description );
1632 
1633             sink.tableHeaderCell_();
1634 
1635             sink.tableRow_();
1636 
1637             if ( generatedReports != null )
1638             {
1639                 for ( final MavenReport report : generatedReports )
1640                 {
1641                     sink.tableRow();
1642                     sink.tableCell();
1643                     sink.link( report.getOutputName() + ".html" );
1644                     sink.text( report.getName( locale ) );
1645                     sink.link_();
1646                     sink.tableCell_();
1647                     sink.tableCell();
1648                     sink.text( report.getDescription( locale ) );
1649                     sink.tableCell_();
1650                     sink.tableRow_();
1651                 }
1652             }
1653 
1654             sink.tableRows_();
1655 
1656             sink.table_();
1657 
1658             sink.section2_();
1659 
1660             sink.section1_();
1661         }
1662     }
1663 }