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