001package org.apache.maven.plugin.plugin;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.io.FileNotFoundException;
024import java.io.FileReader;
025import java.util.ArrayList;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Locale;
029import java.util.Map;
030import java.util.ResourceBundle;
031import java.util.Set;
032
033import org.apache.maven.artifact.Artifact;
034import org.apache.maven.artifact.repository.ArtifactRepository;
035import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
036import org.apache.maven.artifact.versioning.VersionRange;
037import org.apache.maven.doxia.sink.Sink;
038import org.apache.maven.doxia.siterenderer.Renderer;
039import org.apache.maven.execution.RuntimeInformation;
040import org.apache.maven.model.Plugin;
041import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
042import org.apache.maven.plugin.descriptor.MojoDescriptor;
043import org.apache.maven.plugin.descriptor.PluginDescriptor;
044import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
045import org.apache.maven.plugins.annotations.Component;
046import org.apache.maven.plugins.annotations.Execute;
047import org.apache.maven.plugins.annotations.LifecyclePhase;
048import org.apache.maven.plugins.annotations.Mojo;
049import org.apache.maven.plugins.annotations.Parameter;
050import org.apache.maven.plugins.plugin.descriptor.MNG6109PluginDescriptorBuilder;
051import org.apache.maven.project.MavenProject;
052import org.apache.maven.reporting.AbstractMavenReport;
053import org.apache.maven.reporting.AbstractMavenReportRenderer;
054import org.apache.maven.reporting.MavenReportException;
055import org.apache.maven.tools.plugin.DefaultPluginToolsRequest;
056import org.apache.maven.tools.plugin.PluginToolsRequest;
057import org.apache.maven.tools.plugin.extractor.ExtractionException;
058import org.apache.maven.tools.plugin.generator.GeneratorException;
059import org.apache.maven.tools.plugin.generator.GeneratorUtils;
060import org.apache.maven.tools.plugin.generator.PluginXdocGenerator;
061import org.apache.maven.tools.plugin.scanner.MojoScanner;
062import org.apache.maven.tools.plugin.util.PluginUtils;
063import org.codehaus.plexus.component.repository.ComponentDependency;
064import org.codehaus.plexus.configuration.PlexusConfigurationException;
065import org.codehaus.plexus.util.StringUtils;
066import org.codehaus.plexus.util.xml.Xpp3Dom;
067
068/**
069 * Generates the Plugin's documentation report: <code>plugin-info.html</code> plugin overview page,
070 * and one <code><i>goal</i>-mojo.html</code> per goal.
071 *
072 * @author <a href="snicoll@apache.org">Stephane Nicoll</a>
073 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
074 * @since 2.0
075 */
076@Mojo( name = "report", threadSafe = true )
077@Execute( phase = LifecyclePhase.PROCESS_CLASSES )
078public class PluginReport
079    extends AbstractMavenReport
080{
081    /**
082     * Report output directory for mojos' documentation.
083     */
084    @Parameter( defaultValue = "${project.build.directory}/generated-site/xdoc" )
085    private File outputDirectory;
086
087    /**
088     * Doxia Site Renderer.
089     */
090    @Component
091    private Renderer siteRenderer;
092
093    /**
094     * The Maven Project.
095     */
096    @Parameter( defaultValue = "${project}", readonly = true )
097    private MavenProject project;
098
099    /**
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}