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