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.LinkedHashSet;
028import java.util.List;
029import java.util.Locale;
030import java.util.Map;
031import java.util.ResourceBundle;
032
033import org.apache.maven.artifact.repository.ArtifactRepository;
034import org.apache.maven.doxia.sink.Sink;
035import org.apache.maven.doxia.siterenderer.Renderer;
036import org.apache.maven.model.Plugin;
037import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
038import org.apache.maven.plugin.descriptor.MojoDescriptor;
039import org.apache.maven.plugin.descriptor.PluginDescriptor;
040import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
041import org.apache.maven.plugins.annotations.Component;
042import org.apache.maven.plugins.annotations.Execute;
043import org.apache.maven.plugins.annotations.LifecyclePhase;
044import org.apache.maven.plugins.annotations.Mojo;
045import org.apache.maven.plugins.annotations.Parameter;
046import org.apache.maven.plugins.plugin.descriptor.MNG6109PluginDescriptorBuilder;
047import org.apache.maven.project.MavenProject;
048import org.apache.maven.reporting.AbstractMavenReport;
049import org.apache.maven.reporting.AbstractMavenReportRenderer;
050import org.apache.maven.reporting.MavenReportException;
051import org.apache.maven.rtinfo.RuntimeInformation;
052import org.apache.maven.tools.plugin.DefaultPluginToolsRequest;
053import org.apache.maven.tools.plugin.PluginToolsRequest;
054import org.apache.maven.tools.plugin.extractor.ExtractionException;
055import org.apache.maven.tools.plugin.generator.GeneratorException;
056import org.apache.maven.tools.plugin.generator.GeneratorUtils;
057import org.apache.maven.tools.plugin.generator.PluginXdocGenerator;
058import org.apache.maven.tools.plugin.scanner.MojoScanner;
059import org.apache.maven.tools.plugin.util.PluginUtils;
060import org.codehaus.plexus.component.repository.ComponentDependency;
061import org.codehaus.plexus.configuration.PlexusConfigurationException;
062import org.codehaus.plexus.util.StringUtils;
063import org.codehaus.plexus.util.xml.Xpp3Dom;
064
065/**
066 * Generates the Plugin's documentation report: <code>plugin-info.html</code> plugin overview page,
067 * and one <code><i>goal</i>-mojo.html</code> per goal.
068 *
069 * @author <a href="snicoll@apache.org">Stephane Nicoll</a>
070 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
071 * @since 2.0
072 */
073@Mojo( name = "report", threadSafe = true )
074@Execute( phase = LifecyclePhase.PROCESS_CLASSES )
075public class PluginReport
076    extends AbstractMavenReport
077{
078    /**
079     * Report output directory for mojos' documentation.
080     */
081    @Parameter( defaultValue = "${project.build.directory}/generated-site/xdoc" )
082    private File outputDirectory;
083
084    /**
085     * Doxia Site Renderer.
086     */
087    @Component
088    private Renderer siteRenderer;
089
090    /**
091     * The Maven Project.
092     */
093    @Parameter( defaultValue = "${project}", readonly = true )
094    private MavenProject project;
095
096    /**
097     * Mojo scanner tools.
098     */
099    @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}