View Javadoc
1   package org.apache.maven.plugin.plugin;
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.FileNotFoundException;
24  import java.io.FileReader;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.ResourceBundle;
31  import java.util.Set;
32  
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.artifact.repository.ArtifactRepository;
35  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
36  import org.apache.maven.artifact.versioning.VersionRange;
37  import org.apache.maven.doxia.sink.Sink;
38  import org.apache.maven.doxia.siterenderer.Renderer;
39  import org.apache.maven.execution.RuntimeInformation;
40  import org.apache.maven.model.Plugin;
41  import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
42  import org.apache.maven.plugin.descriptor.MojoDescriptor;
43  import org.apache.maven.plugin.descriptor.PluginDescriptor;
44  import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
45  import org.apache.maven.plugins.annotations.Component;
46  import org.apache.maven.plugins.annotations.Execute;
47  import org.apache.maven.plugins.annotations.LifecyclePhase;
48  import org.apache.maven.plugins.annotations.Mojo;
49  import org.apache.maven.plugins.annotations.Parameter;
50  import org.apache.maven.plugins.plugin.descriptor.MNG6109PluginDescriptorBuilder;
51  import org.apache.maven.project.MavenProject;
52  import org.apache.maven.reporting.AbstractMavenReport;
53  import org.apache.maven.reporting.AbstractMavenReportRenderer;
54  import org.apache.maven.reporting.MavenReportException;
55  import org.apache.maven.tools.plugin.DefaultPluginToolsRequest;
56  import org.apache.maven.tools.plugin.PluginToolsRequest;
57  import org.apache.maven.tools.plugin.extractor.ExtractionException;
58  import org.apache.maven.tools.plugin.generator.GeneratorException;
59  import org.apache.maven.tools.plugin.generator.GeneratorUtils;
60  import org.apache.maven.tools.plugin.generator.PluginXdocGenerator;
61  import org.apache.maven.tools.plugin.scanner.MojoScanner;
62  import org.apache.maven.tools.plugin.util.PluginUtils;
63  import org.codehaus.plexus.component.repository.ComponentDependency;
64  import org.codehaus.plexus.configuration.PlexusConfigurationException;
65  import org.codehaus.plexus.util.StringUtils;
66  import org.codehaus.plexus.util.xml.Xpp3Dom;
67  
68  /**
69   * Generates the Plugin's documentation report: <code>plugin-info.html</code> plugin overview page,
70   * and one <code><i>goal</i>-mojo.html</code> per goal.
71   *
72   * @author <a href="snicoll@apache.org">Stephane Nicoll</a>
73   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
74   * @since 2.0
75   */
76  @Mojo( name = "report", threadSafe = true )
77  @Execute( phase = LifecyclePhase.PROCESS_CLASSES )
78  public class PluginReport
79      extends AbstractMavenReport
80  {
81      /**
82       * Report output directory for mojos' documentation.
83       */
84      @Parameter( defaultValue = "${project.build.directory}/generated-site/xdoc" )
85      private File outputDirectory;
86  
87      /**
88       * Doxia Site Renderer.
89       */
90      @Component
91      private Renderer siteRenderer;
92  
93      /**
94       * The Maven Project.
95       */
96      @Parameter( defaultValue = "${project}", readonly = true )
97      private MavenProject project;
98  
99      /**
100      * Mojo scanner tools.
101      */
102     @Component
103     protected MojoScanner mojoScanner;
104 
105     /**
106      * The file encoding of the source files.
107      *
108      * @since 2.7
109      */
110     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
111     private String encoding;
112 
113     /**
114      * Specify some requirements to execute this plugin.
115      * Example:
116      * <pre>
117      * &lt;requirements&gt;
118      *   &lt;maven&gt;2.0&lt;/maven&gt;
119      *   &lt;jdk&gt;1.4&lt;/jdk&gt;
120      *   &lt;memory&gt;256m&lt;/memory&gt;
121      *   &lt;diskSpace&gt;1m&lt;/diskSpace&gt;
122      *   &lt;others&gt;
123      *     &lt;property&gt;
124      *       &lt;name&gt;SVN&lt;/name&gt;
125      *       &lt;value&gt;1.4.6&lt;/value&gt;
126      *     &lt;/property&gt;
127      *   &lt;/others&gt;
128      * &lt;/requirements&gt;
129      * </pre>
130      * 
131      * If not is specified, Maven requirement is extracted from
132      * <code>&lt;project&gt;&lt;prerequisites&gt;&lt;maven&gt;</code>
133      * and JDK requirement is extracted from maven-compiler-plugin configuration.
134      */
135     @Parameter
136     private Requirements requirements;
137 
138     /**
139      * The goal prefix that will appear before the ":".
140      * By default, this plugin applies a heuristic to derive a heuristic from
141      * the plugin's artifactId.
142      * <p/>
143      * It removes any occurrences of the regular expression <strong>-?maven-?</strong>,
144      * and then removes any occurrences of <strong>-?plugin-?</strong>.
145      * <p>
146      * For example, horsefeature-maven-plugin becomes horsefeature.
147      * </p>
148      * <p>
149      * (There is a special case for maven-plugin-plugin: it is mapped to 'plugin')
150      * </p>
151      *
152      * @since 2.4
153      */
154     @Parameter( property = "goalPrefix" )
155     protected String goalPrefix;
156 
157     /**
158      * Set this to "true" to skip invoking any goals or reports of the plugin.
159      *
160      * @since 2.8
161      */
162     @Parameter( defaultValue = "false", property = "maven.plugin.skip" )
163     private boolean skip;
164 
165     /**
166      * Set this to "true" to skip generating the report.
167      *
168      * @since 2.8
169      */
170     @Parameter( defaultValue = "false", property = "maven.plugin.report.skip" )
171     private boolean skipReport;
172 
173     /**
174      * The set of dependencies for the current project
175      *
176      * @since 3.0
177      */
178     @Parameter( defaultValue = "${project.artifacts}", required = true, readonly = true )
179     protected Set<Artifact> dependencies;
180 
181     /**
182      * List of Remote Repositories used by the resolver
183      *
184      * @since 3.0
185      */
186     @Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )
187     protected List<ArtifactRepository> remoteRepos;
188 
189     /**
190      * Location of the local repository.
191      *
192      * @since 3.0
193      */
194     @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
195     protected ArtifactRepository local;
196     
197     /**
198      * @since 3.5.1
199      */
200     @Component
201     private RuntimeInformation rtInfo;
202 
203     /**
204      * Path to {@code plugin.xml} plugin descriptor to generate the report from.
205      *
206      * @since 3.5.1
207      */
208     @Parameter( defaultValue = "${project.build.outputDirectory}/META-INF/maven/plugin.xml", required = true,
209                     readonly = true )
210     private File pluginXmlFile;
211 
212     /**
213      * {@inheritDoc}
214      */
215     @Override
216     protected Renderer getSiteRenderer()
217     {
218         return siteRenderer;
219     }
220 
221     /**
222      * {@inheritDoc}
223      */
224     @Override
225     protected String getOutputDirectory()
226     {
227         // PLUGIN-191: output directory of plugin.html, not *-mojo.xml
228         return project.getReporting().getOutputDirectory();
229     }
230 
231     /**
232      * {@inheritDoc}
233      */
234     @Override
235     protected MavenProject getProject()
236     {
237         return project;
238     }
239 
240     /**
241      * {@inheritDoc}
242      */
243     @Override
244     public boolean canGenerateReport()
245     {
246         return pluginXmlFile != null && pluginXmlFile.isFile() && pluginXmlFile.canRead();
247     }
248 
249     /**
250      * {@inheritDoc}
251      */
252     @Override
253     protected void executeReport( Locale locale )
254         throws MavenReportException
255     {
256         if ( !canGenerateReport() )
257         {
258             return;
259         }
260         if ( skip || skipReport )
261         {
262             getLog().info( "Maven Plugin Plugin Report generation skipped." );
263             return;
264         }
265 
266         PluginDescriptor pluginDescriptor = extractPluginDescriptor();
267 
268         // Generate the mojos' documentation
269         generateMojosDocumentation( pluginDescriptor, locale );
270 
271         // Write the overview
272         PluginOverviewRenderer r =
273             new PluginOverviewRenderer( project, requirements, getSink(), pluginDescriptor, locale );
274         r.render();
275     }
276 
277     private PluginDescriptor extractPluginDescriptor()
278         throws MavenReportException
279     {
280         PluginDescriptorBuilder builder = getPluginDescriptorBuilder();
281         
282         try
283         {
284             return builder.build( new FileReader( pluginXmlFile ) );
285         }
286         catch ( FileNotFoundException e )
287         {
288             getLog().debug( "Failed to read " + pluginXmlFile + ", fall back to mojoScanner" );
289         }
290         catch ( PlexusConfigurationException e )
291         {
292             getLog().debug( "Failed to read " + pluginXmlFile + ", fall back to mojoScanner" );
293         }
294 
295         // Copy from AbstractGeneratorMojo#execute()
296         String defaultGoalPrefix = PluginDescriptor.getGoalPrefixFromArtifactId( project.getArtifactId() );
297         if ( goalPrefix == null )
298         {
299             goalPrefix = defaultGoalPrefix;
300         }
301         else
302         {
303             getLog().warn( "\n\nGoal prefix is specified as: '" + goalPrefix + "'. Maven currently expects it to be '"
304                                + defaultGoalPrefix + "'.\n" );
305         }
306 
307         // TODO: could use this more, eg in the writing of the plugin descriptor!
308         PluginDescriptor pluginDescriptor = new PluginDescriptor();
309 
310         pluginDescriptor.setGroupId( project.getGroupId() );
311 
312         pluginDescriptor.setArtifactId( project.getArtifactId() );
313 
314         pluginDescriptor.setVersion( project.getVersion() );
315 
316         pluginDescriptor.setGoalPrefix( goalPrefix );
317 
318         try
319         {
320             @SuppressWarnings( "unchecked" )
321             List<ComponentDependency> deps = GeneratorUtils.toComponentDependencies( project.getRuntimeDependencies() );
322             pluginDescriptor.setDependencies( deps );
323 
324             PluginToolsRequest request = new DefaultPluginToolsRequest( project, pluginDescriptor );
325             request.setEncoding( encoding );
326             request.setSkipErrorNoDescriptorsFound( true );
327             request.setDependencies( dependencies );
328             request.setLocal( this.local );
329             request.setRemoteRepos( this.remoteRepos );
330 
331             try
332             {
333                 mojoScanner.populatePluginDescriptor( request );
334             }
335             catch ( InvalidPluginDescriptorException e )
336             {
337                 // this is OK, it happens to lifecycle plugins. Allow generation to proceed.
338                 getLog().debug( "Plugin without mojos.", e );
339             }
340         }
341         catch ( ExtractionException e )
342         {
343             throw new MavenReportException( "Error extracting plugin descriptor: \'" + e.getLocalizedMessage() + "\'",
344                                             e );
345         }
346         return pluginDescriptor;
347     }
348 
349     /**
350      * Return the pluginDescriptorBuilder to use based on the Maven version: either use the original from the 
351      * maven-plugin-api or a patched version for Maven versions before the MNG-6109 fix 
352      * (because of Maven MNG-6109 bug that won't give accurate 'since' info when reading plugin.xml).
353      * 
354      * @return the proper pluginDescriptorBuilder
355      * @see https://issues.apache.org/jira/browse/MNG-6109
356      * @see https://issues.apache.org/jira/browse/MPLUGIN-319
357      */
358     private PluginDescriptorBuilder getPluginDescriptorBuilder()
359     {
360         PluginDescriptorBuilder pluginDescriptorBuilder;
361         try
362         {
363             VersionRange versionRange = VersionRange.createFromVersionSpec( "(3.3.9,)" );
364             if ( versionRange.containsVersion( rtInfo.getApplicationVersion() ) )
365             {
366                 pluginDescriptorBuilder = new PluginDescriptorBuilder();
367             }
368             else
369             {
370                 pluginDescriptorBuilder = new MNG6109PluginDescriptorBuilder();
371             }
372         }
373         catch ( InvalidVersionSpecificationException e )
374         {
375             return new MNG6109PluginDescriptorBuilder();
376         }
377         
378         return pluginDescriptorBuilder;
379     }
380 
381     /**
382      * {@inheritDoc}
383      */
384     @Override
385     public String getDescription( Locale locale )
386     {
387         return getBundle( locale ).getString( "report.plugin.description" );
388     }
389 
390     /**
391      * {@inheritDoc}
392      */
393     @Override
394     public String getName( Locale locale )
395     {
396         return getBundle( locale ).getString( "report.plugin.name" );
397     }
398 
399     /**
400      * {@inheritDoc}
401      */
402     @Override
403     public String getOutputName()
404     {
405         return "plugin-info";
406     }
407 
408     /**
409      * Generate the mojos documentation, as xdoc files.
410      *
411      * @param pluginDescriptor not null
412      * @param locale           not null
413      * @throws MavenReportException if any
414      */
415     private void generateMojosDocumentation( PluginDescriptor pluginDescriptor, Locale locale )
416         throws MavenReportException
417     {
418         try
419         {
420             File outputDir = outputDirectory;
421             outputDir.mkdirs();
422 
423             PluginXdocGenerator generator = new PluginXdocGenerator( project, locale );
424             PluginToolsRequest pluginToolsRequest = new DefaultPluginToolsRequest( project, pluginDescriptor );
425             generator.execute( outputDir, pluginToolsRequest );
426         }
427         catch ( GeneratorException e )
428         {
429             throw new MavenReportException( "Error writing plugin documentation", e );
430         }
431 
432     }
433 
434     /**
435      * @param locale not null
436      * @return the bundle for this report
437      */
438     protected static ResourceBundle getBundle( Locale locale )
439     {
440         return ResourceBundle.getBundle( "plugin-report", locale, PluginReport.class.getClassLoader() );
441     }
442 
443     /**
444      * Generates an overview page with the list of goals
445      * and a link to the goal's page.
446      */
447     static class PluginOverviewRenderer
448         extends AbstractMavenReportRenderer
449     {
450         private final MavenProject project;
451 
452         private final Requirements requirements;
453 
454         private final PluginDescriptor pluginDescriptor;
455 
456         private final Locale locale;
457 
458         /**
459          * @param project          not null
460          * @param requirements     not null
461          * @param sink             not null
462          * @param pluginDescriptor not null
463          * @param locale           not null
464          */
465         PluginOverviewRenderer( MavenProject project, Requirements requirements, Sink sink,
466                                 PluginDescriptor pluginDescriptor, Locale locale )
467         {
468             super( sink );
469 
470             this.project = project;
471 
472             this.requirements = ( requirements == null ? new Requirements() : requirements );
473 
474             this.pluginDescriptor = pluginDescriptor;
475 
476             this.locale = locale;
477         }
478 
479         /**
480          * {@inheritDoc}
481          */
482         @Override
483         public String getTitle()
484         {
485             return getBundle( locale ).getString( "report.plugin.title" );
486         }
487 
488         /**
489          * {@inheritDoc}
490          */
491         @Override
492         @SuppressWarnings( { "unchecked", "rawtypes" } )
493         public void renderBody()
494         {
495             startSection( getTitle() );
496 
497             if ( !( pluginDescriptor.getMojos() != null && pluginDescriptor.getMojos().size() > 0 ) )
498             {
499                 paragraph( getBundle( locale ).getString( "report.plugin.goals.nogoal" ) );
500                 endSection();
501                 return;
502             }
503 
504             paragraph( getBundle( locale ).getString( "report.plugin.goals.intro" ) );
505 
506             boolean hasMavenReport = false;
507             for ( Iterator<MojoDescriptor> i = pluginDescriptor.getMojos().iterator(); i.hasNext(); )
508             {
509                 MojoDescriptor mojo = i.next();
510 
511                 if ( GeneratorUtils.isMavenReport( mojo.getImplementation(), project ) )
512                 {
513                     hasMavenReport = true;
514                 }
515             }
516 
517             startTable();
518 
519             String goalColumnName = getBundle( locale ).getString( "report.plugin.goals.column.goal" );
520             String isMavenReport = getBundle( locale ).getString( "report.plugin.goals.column.isMavenReport" );
521             String descriptionColumnName = getBundle( locale ).getString( "report.plugin.goals.column.description" );
522             if ( hasMavenReport )
523             {
524                 tableHeader( new String[]{ goalColumnName, isMavenReport, descriptionColumnName } );
525             }
526             else
527             {
528                 tableHeader( new String[]{ goalColumnName, descriptionColumnName } );
529             }
530 
531             List<MojoDescriptor> mojos = new ArrayList<MojoDescriptor>();
532             mojos.addAll( pluginDescriptor.getMojos() );
533             PluginUtils.sortMojos( mojos );
534             for ( MojoDescriptor mojo : mojos )
535             {
536                 String goalName = mojo.getFullGoalName();
537 
538                 /*
539                  * Added ./ to define a relative path
540                  * @see AbstractMavenReportRenderer#getValidHref(java.lang.String)
541                  */
542                 String goalDocumentationLink = "./" + mojo.getGoal() + "-mojo.html";
543 
544                 String description;
545                 if ( StringUtils.isNotEmpty( mojo.getDeprecated() ) )
546                 {
547                     description =
548                         "<strong>" + getBundle( locale ).getString( "report.plugin.goal.deprecated" ) + "</strong> "
549                             + GeneratorUtils.makeHtmlValid( mojo.getDeprecated() );
550                 }
551                 else if ( StringUtils.isNotEmpty( mojo.getDescription() ) )
552                 {
553                     description = GeneratorUtils.makeHtmlValid( mojo.getDescription() );
554                 }
555                 else
556                 {
557                     description = getBundle( locale ).getString( "report.plugin.goal.nodescription" );
558                 }
559 
560                 sink.tableRow();
561                 tableCell( createLinkPatternedText( goalName, goalDocumentationLink ) );
562                 if ( hasMavenReport )
563                 {
564                     if ( GeneratorUtils.isMavenReport( mojo.getImplementation(), project ) )
565                     {
566                         sink.tableCell();
567                         sink.text( getBundle( locale ).getString( "report.plugin.isReport" ) );
568                         sink.tableCell_();
569                     }
570                     else
571                     {
572                         sink.tableCell();
573                         sink.text( getBundle( locale ).getString( "report.plugin.isNotReport" ) );
574                         sink.tableCell_();
575                     }
576                 }
577                 tableCell( description, true );
578                 sink.tableRow_();
579             }
580 
581             endTable();
582 
583             startSection( getBundle( locale ).getString( "report.plugin.systemrequirements" ) );
584 
585             paragraph( getBundle( locale ).getString( "report.plugin.systemrequirements.intro" ) );
586 
587             startTable();
588 
589             String maven = discoverMavenRequirement( project, requirements );
590             sink.tableRow();
591             tableCell( getBundle( locale ).getString( "report.plugin.systemrequirements.maven" ) );
592             tableCell( ( maven != null
593                 ? maven
594                 : getBundle( locale ).getString( "report.plugin.systemrequirements.nominimum" ) ) );
595             sink.tableRow_();
596 
597             String jdk = discoverJdkRequirement( project, requirements );
598             sink.tableRow();
599             tableCell( getBundle( locale ).getString( "report.plugin.systemrequirements.jdk" ) );
600             tableCell(
601                 ( jdk != null ? jdk : getBundle( locale ).getString( "report.plugin.systemrequirements.nominimum" ) ) );
602             sink.tableRow_();
603 
604             sink.tableRow();
605             tableCell( getBundle( locale ).getString( "report.plugin.systemrequirements.memory" ) );
606             tableCell( ( StringUtils.isNotEmpty( requirements.getMemory() )
607                 ? requirements.getMemory()
608                 : getBundle( locale ).getString( "report.plugin.systemrequirements.nominimum" ) ) );
609             sink.tableRow_();
610 
611             sink.tableRow();
612             tableCell( getBundle( locale ).getString( "report.plugin.systemrequirements.diskspace" ) );
613             tableCell( ( StringUtils.isNotEmpty( requirements.getDiskSpace() )
614                 ? requirements.getDiskSpace()
615                 : getBundle( locale ).getString( "report.plugin.systemrequirements.nominimum" ) ) );
616             sink.tableRow_();
617 
618             if ( requirements.getOthers() != null && requirements.getOthers().size() > 0 )
619             {
620                 for ( Iterator it = requirements.getOthers().keySet().iterator(); it.hasNext(); )
621                 {
622                     String key = it.next().toString();
623 
624                     sink.tableRow();
625                     tableCell( key );
626                     tableCell( ( StringUtils.isNotEmpty( requirements.getOthers().getProperty( key ) )
627                         ? requirements.getOthers().getProperty( key )
628                         : getBundle( locale ).getString( "report.plugin.systemrequirements.nominimum" ) ) );
629                     sink.tableRow_();
630                 }
631             }
632             endTable();
633 
634             endSection();
635 
636             renderUsageSection( hasMavenReport );
637 
638             endSection();
639         }
640 
641         /**
642          * Render the section about the usage of the plugin.
643          *
644          * @param hasMavenReport If the plugin has a report or not
645          */
646         private void renderUsageSection( boolean hasMavenReport )
647         {
648             startSection( getBundle( locale ).getString( "report.plugin.usage" ) );
649 
650             // Configuration
651             sink.paragraph();
652             text( getBundle( locale ).getString( "report.plugin.usage.intro" ) );
653             sink.paragraph_();
654 
655             StringBuilder sb = new StringBuilder();
656             sb.append( "<project>" ).append( '\n' );
657             sb.append( "  ..." ).append( '\n' );
658             sb.append( "  <build>" ).append( '\n' );
659             sb.append(
660                 "    <!-- " + getBundle( locale ).getString( "report.plugin.usage.pluginManagement" ) + " -->" ).append(
661                 '\n' );
662             sb.append( "    <pluginManagement>" ).append( '\n' );
663             sb.append( "      <plugins>" ).append( '\n' );
664             sb.append( "        <plugin>" ).append( '\n' );
665             sb.append( "          <groupId>" ).append( pluginDescriptor.getGroupId() ).append( "</groupId>" ).append(
666                 '\n' );
667             sb.append( "          <artifactId>" ).append( pluginDescriptor.getArtifactId() ).append(
668                 "</artifactId>" ).append( '\n' );
669             sb.append( "          <version>" ).append( pluginDescriptor.getVersion() ).append( "</version>" ).append(
670                 '\n' );
671             sb.append( "        </plugin>" ).append( '\n' );
672             sb.append( "        ..." ).append( '\n' );
673             sb.append( "      </plugins>" ).append( '\n' );
674             sb.append( "    </pluginManagement>" ).append( '\n' );
675             sb.append( "    <!-- " + getBundle( locale ).getString( "report.plugin.usage.plugins" ) + " -->" ).append(
676                 '\n' );
677             sb.append( "    <plugins>" ).append( '\n' );
678             sb.append( "      <plugin>" ).append( '\n' );
679             sb.append( "        <groupId>" ).append( pluginDescriptor.getGroupId() ).append( "</groupId>" ).append(
680                 '\n' );
681             sb.append( "        <artifactId>" ).append( pluginDescriptor.getArtifactId() ).append(
682                 "</artifactId>" ).append( '\n' );
683             sb.append( "        <version>" ).append( pluginDescriptor.getVersion() ).append( "</version>" ).append(
684                 '\n' );
685             sb.append( "      </plugin>" ).append( '\n' );
686             sb.append( "      ..." ).append( '\n' );
687             sb.append( "    </plugins>" ).append( '\n' );
688             sb.append( "  </build>" ).append( '\n' );
689 
690             if ( hasMavenReport )
691             {
692                 sb.append( "  ..." ).append( '\n' );
693                 sb.append(
694                     "  <!-- " + getBundle( locale ).getString( "report.plugin.usage.reporting" ) + " -->" ).append(
695                     '\n' );
696                 sb.append( "  <reporting>" ).append( '\n' );
697                 sb.append( "    <plugins>" ).append( '\n' );
698                 sb.append( "      <plugin>" ).append( '\n' );
699                 sb.append( "        <groupId>" ).append( pluginDescriptor.getGroupId() ).append( "</groupId>" ).append(
700                     '\n' );
701                 sb.append( "        <artifactId>" ).append( pluginDescriptor.getArtifactId() ).append(
702                     "</artifactId>" ).append( '\n' );
703                 sb.append( "        <version>" ).append( pluginDescriptor.getVersion() ).append( "</version>" ).append(
704                     '\n' );
705                 sb.append( "      </plugin>" ).append( '\n' );
706                 sb.append( "      ..." ).append( '\n' );
707                 sb.append( "    </plugins>" ).append( '\n' );
708                 sb.append( "  </reporting>" ).append( '\n' );
709             }
710 
711             sb.append( "  ..." ).append( '\n' );
712             sb.append( "</project>" ).append( '\n' );
713 
714             verbatimText( sb.toString() );
715 
716             sink.paragraph();
717             linkPatternedText( getBundle( locale ).getString( "report.plugin.configuration.end" ) );
718             sink.paragraph_();
719 
720             endSection();
721         }
722 
723         /**
724          * Try to lookup on the Maven prerequisites property.
725          * If not specified, uses the value defined by the user.
726          *
727          * @param project      not null
728          * @param requirements not null
729          * @return the Maven version
730          */
731         private static String discoverMavenRequirement( MavenProject project, Requirements requirements )
732         {
733             String maven = requirements.getMaven();
734             if ( maven == null )
735             {
736                 maven = ( project.getPrerequisites() != null ? project.getPrerequisites().getMaven() : null );
737             }
738             if ( maven == null )
739             {
740                 maven = "2.0";
741             }
742 
743             return maven;
744         }
745 
746         /**
747          * <ol>
748          * <li>use configured jdk requirement</li>
749          * <li>use <code>target</code> configuration of <code>org.apache.maven.plugins:maven-compiler-plugin</code></li>
750          * <li>use <code>target</code> configuration of <code>org.apache.maven.plugins:maven-compiler-plugin</code> in
751          * <code>pluginManagement</code></li>
752          * <li>use <code>maven.compiler.target</code> property</li>
753          * </ol>
754          *
755          * @param project      not null
756          * @param requirements not null
757          * @return the JDK version
758          */
759         private static String discoverJdkRequirement( MavenProject project, Requirements requirements )
760         {
761             String jdk = requirements.getJdk();
762 
763             if ( jdk != null )
764             {
765                 return jdk;
766             }
767 
768             @SuppressWarnings( "unchecked" )
769             Plugin compiler = getCompilerPlugin( project.getBuild().getPluginsAsMap() );
770             if ( compiler == null )
771             {
772                 compiler = getCompilerPlugin( project.getPluginManagement().getPluginsAsMap() );
773             }
774 
775             jdk = getPluginParameter( compiler, "target" );
776             if ( jdk != null )
777             {
778                 return jdk;
779             }
780 
781             // default value
782             jdk = project.getProperties().getProperty( "maven.compiler.target" );
783             if ( jdk != null )
784             {
785                 return jdk;
786             }
787 
788             // return "1.5" by default?
789 
790             String version = ( compiler == null ) ? null : compiler.getVersion();
791 
792             if ( version != null )
793             {
794                 return "Default target for maven-compiler-plugin version " + version;
795             }
796 
797             return "Unknown";
798         }
799 
800         private static Plugin getCompilerPlugin( Map<String, Object> pluginsAsMap )
801         {
802             return (Plugin) pluginsAsMap.get( "org.apache.maven.plugins:maven-compiler-plugin" );
803         }
804 
805         private static String getPluginParameter( Plugin plugin, String parameter )
806         {
807             if ( plugin != null )
808             {
809                 Xpp3Dom pluginConf = (Xpp3Dom) plugin.getConfiguration();
810 
811                 if ( pluginConf != null )
812                 {
813                     Xpp3Dom target = pluginConf.getChild( parameter );
814 
815                     if ( target != null )
816                     {
817                         return target.getValue();
818                     }
819                 }
820             }
821 
822             return null;
823         }
824     }
825 }