001package org.apache.maven.tools.plugin.generator;
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.IOException;
024import java.io.OutputStreamWriter;
025import java.io.PrintWriter;
026import java.io.Writer;
027import java.net.URI;
028import java.net.URISyntaxException;
029import java.text.MessageFormat;
030import java.util.ArrayList;
031import java.util.Iterator;
032import java.util.List;
033import java.util.Locale;
034import java.util.ResourceBundle;
035import java.util.regex.Matcher;
036import java.util.regex.Pattern;
037
038import org.apache.maven.plugin.descriptor.MojoDescriptor;
039import org.apache.maven.plugin.descriptor.Parameter;
040import org.apache.maven.project.MavenProject;
041import org.apache.maven.tools.plugin.EnhancedParameterWrapper;
042import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
043import org.apache.maven.tools.plugin.PluginToolsRequest;
044import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator;
045import org.codehaus.plexus.util.StringUtils;
046import org.codehaus.plexus.util.io.CachingOutputStream;
047import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
048import org.codehaus.plexus.util.xml.XMLWriter;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052import static java.nio.charset.StandardCharsets.UTF_8;
053
054/**
055 * Generate <a href="https://maven.apache.org/doxia/references/xdoc-format.html">xdoc documentation</a> for each mojo.
056 */
057public class PluginXdocGenerator
058    implements Generator
059{
060    /**
061     * Regular expression matching an XHTML link
062     * group 1 = link target, group 2 = link label
063     */
064    private static final Pattern HTML_LINK_PATTERN = Pattern.compile( "<a href=\\\"([^\\\"]*)\\\">(.*?)</a>" );
065
066    private static final Logger LOG = LoggerFactory.getLogger( PluginXdocGenerator.class );
067
068    /**
069     * locale
070     */
071    private final Locale locale;
072
073    /**
074     * project
075     */
076    private final MavenProject project;
077
078    /**
079     * The directory where the generated site is written.
080     * Used for resolving relative links to javadoc.
081     */
082    private final File reportOutputDirectory;
083
084    private final boolean disableInternalJavadocLinkValidation;
085
086    /**
087     * Default constructor using <code>Locale.ENGLISH</code> as locale.
088     * Used only in test cases.
089     */
090    public PluginXdocGenerator()
091    {
092        this( null );
093    }
094
095    /**
096     * Constructor using <code>Locale.ENGLISH</code> as locale.
097     *
098     * @param project not null Maven project.
099     */
100    public PluginXdocGenerator( MavenProject project )
101    {
102        this( project, Locale.ENGLISH, new File( "" ).getAbsoluteFile(), false );
103    }
104
105    /**
106     * @param project not null.
107     * @param locale  not null wanted locale.
108     */
109    public PluginXdocGenerator( MavenProject project, Locale locale, File reportOutputDirectory,
110                                boolean disableInternalJavadocLinkValidation )
111    {
112        this.project = project;
113        if ( locale == null )
114        {
115            this.locale = Locale.ENGLISH;
116        }
117        else
118        {
119            this.locale = locale;
120        }
121        this.reportOutputDirectory = reportOutputDirectory;
122        this.disableInternalJavadocLinkValidation = disableInternalJavadocLinkValidation;
123    }
124
125
126    /**
127     * {@inheritDoc}
128     */
129    @Override
130    public void execute( File destinationDirectory, PluginToolsRequest request )
131        throws GeneratorException
132    {
133        try
134        {
135            if ( request.getPluginDescriptor().getMojos() != null )
136            {
137                List<MojoDescriptor> mojos = request.getPluginDescriptor().getMojos();
138                for ( MojoDescriptor descriptor : mojos )
139                {
140                    processMojoDescriptor( descriptor, destinationDirectory );
141                }
142            }
143        }
144        catch ( IOException e )
145        {
146            throw new GeneratorException( e.getMessage(), e );
147        }
148
149    }
150
151    /**
152     * @param mojoDescriptor       not null
153     * @param destinationDirectory not null
154     * @throws IOException if any
155     */
156    protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, File destinationDirectory )
157        throws IOException
158    {
159        File outputFile = new File( destinationDirectory, getMojoFilename( mojoDescriptor, "xml" ) );
160        try ( Writer writer = new OutputStreamWriter( new CachingOutputStream( outputFile ), UTF_8 ) )
161        {
162            XMLWriter w = new PrettyPrintXMLWriter( new PrintWriter( writer ), UTF_8.name(), null );
163            writeBody( mojoDescriptor, w );
164
165            writer.flush();
166        }
167    }
168
169    /**
170     * @param mojo not null
171     * @param ext  not null
172     * @return the output file name
173     */
174    private String getMojoFilename( MojoDescriptor mojo, String ext )
175    {
176        return mojo.getGoal() + "-mojo." + ext;
177    }
178
179    /**
180     * @param mojoDescriptor not null
181     * @param w              not null
182     */
183    private void writeBody( MojoDescriptor mojoDescriptor, XMLWriter w )
184    {
185        w.startElement( "document" );
186        w.addAttribute( "xmlns", "http://maven.apache.org/XDOC/2.0" );
187        w.addAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" );
188        w.addAttribute( "xsi:schemaLocation",
189                        "http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd" );
190
191        // ----------------------------------------------------------------------
192        //
193        // ----------------------------------------------------------------------
194
195        w.startElement( "properties" );
196
197        w.startElement( "title" );
198        w.writeText( mojoDescriptor.getFullGoalName() );
199        w.endElement(); // title
200
201        w.endElement(); // properties
202
203        // ----------------------------------------------------------------------
204        //
205        // ----------------------------------------------------------------------
206
207        w.startElement( "body" );
208
209        w.startElement( "section" );
210
211        w.addAttribute( "name", mojoDescriptor.getFullGoalName() );
212
213        writeReportNotice( mojoDescriptor, w );
214
215        w.startElement( "p" );
216        w.writeMarkup( getString( "pluginxdoc.mojodescriptor.fullname" ) );
217        w.endElement(); //p
218        w.startElement( "p" );
219        w.writeMarkup( mojoDescriptor.getPluginDescriptor().getGroupId() + ":"
220                           + mojoDescriptor.getPluginDescriptor().getArtifactId() + ":"
221                           + mojoDescriptor.getPluginDescriptor().getVersion() + ":" + mojoDescriptor.getGoal() );
222        w.endElement(); //p
223
224        String context = "goal " + mojoDescriptor.getGoal();
225        if ( StringUtils.isNotEmpty( mojoDescriptor.getDeprecated() ) )
226        {
227            w.startElement( "p" );
228            w.writeMarkup( getString( "pluginxdoc.mojodescriptor.deprecated" ) );
229            w.endElement(); // p
230            w.startElement( "div" );
231            w.writeMarkup( getXhtmlWithValidatedLinks( mojoDescriptor.getDeprecated(), context ) );
232            w.endElement(); // div
233        }
234
235        w.startElement( "p" );
236        w.writeMarkup( getString( "pluginxdoc.description" ) );
237        w.endElement(); //p
238        w.startElement( "div" );
239        if ( StringUtils.isNotEmpty( mojoDescriptor.getDescription() ) )
240        {
241            w.writeMarkup( getXhtmlWithValidatedLinks( mojoDescriptor.getDescription(), context ) );
242        }
243        else
244        {
245            w.writeText( getString( "pluginxdoc.nodescription" ) );
246        }
247        w.endElement(); // div
248
249        writeGoalAttributes( mojoDescriptor, w );
250
251        writeGoalParameterTable( mojoDescriptor, w );
252
253        w.endElement(); // section
254
255        w.endElement(); // body
256
257        w.endElement(); // document
258    }
259
260    /**
261     * @param mojoDescriptor not null
262     * @param w              not null
263     */
264    private void writeReportNotice( MojoDescriptor mojoDescriptor, XMLWriter w )
265    {
266        if ( GeneratorUtils.isMavenReport( mojoDescriptor.getImplementation(), project ) )
267        {
268            w.startElement( "p" );
269            w.writeMarkup( getString( "pluginxdoc.mojodescriptor.notice.note" ) );
270            w.writeText( getString( "pluginxdoc.mojodescriptor.notice.isMavenReport" ) );
271            w.endElement(); //p
272        }
273    }
274
275    /**
276     * @param mojoDescriptor not null
277     * @param w              not null
278     */
279    private void writeGoalAttributes( MojoDescriptor mojoDescriptor, XMLWriter w )
280    {
281        w.startElement( "p" );
282        w.writeMarkup( getString( "pluginxdoc.mojodescriptor.attributes" ) );
283        w.endElement(); //p
284
285        boolean addedUl = false;
286        String value;
287        if ( mojoDescriptor.isProjectRequired() )
288        {
289            addedUl = addUl( w, addedUl );
290            w.startElement( "li" );
291            w.writeMarkup( getString( "pluginxdoc.mojodescriptor.projectRequired" ) );
292            w.endElement(); //li
293        }
294
295        if ( mojoDescriptor.isRequiresReports() )
296        {
297            addedUl = addUl( w, addedUl );
298            w.startElement( "li" );
299            w.writeMarkup( getString( "pluginxdoc.mojodescriptor.reportingMojo" ) );
300            w.endElement(); // li
301        }
302
303        if ( mojoDescriptor.isAggregator() )
304        {
305            addedUl = addUl( w, addedUl );
306            w.startElement( "li" );
307            w.writeMarkup( getString( "pluginxdoc.mojodescriptor.aggregator" ) );
308            w.endElement(); //li
309        }
310
311        if ( mojoDescriptor.isDirectInvocationOnly() )
312        {
313            addedUl = addUl( w, addedUl );
314            w.startElement( "li" );
315            w.writeMarkup( getString( "pluginxdoc.mojodescriptor.directInvocationOnly" ) );
316            w.endElement(); //li
317        }
318
319        value = mojoDescriptor.isDependencyResolutionRequired();
320        if ( StringUtils.isNotEmpty( value ) )
321        {
322            addedUl = addUl( w, addedUl );
323            w.startElement( "li" );
324            w.writeMarkup( format( "pluginxdoc.mojodescriptor.dependencyResolutionRequired", value ) );
325            w.endElement(); //li
326        }
327
328        if ( mojoDescriptor instanceof ExtendedMojoDescriptor )
329        {
330            ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor;
331
332            value = extendedMojoDescriptor.getDependencyCollectionRequired();
333            if ( StringUtils.isNotEmpty( value ) )
334            {
335                addedUl = addUl( w, addedUl );
336                w.startElement( "li" );
337                w.writeMarkup( format( "pluginxdoc.mojodescriptor.dependencyCollectionRequired", value ) );
338                w.endElement(); //li
339            }
340        }
341
342        addedUl = addUl( w, addedUl );
343        w.startElement( "li" );
344        w.writeMarkup( getString( mojoDescriptor.isThreadSafe()
345                ? "pluginxdoc.mojodescriptor.threadSafe"
346                : "pluginxdoc.mojodescriptor.notThreadSafe" ) );
347        w.endElement(); //li
348
349        value = mojoDescriptor.getSince();
350        if ( StringUtils.isNotEmpty( value ) )
351        {
352            addedUl = addUl( w, addedUl );
353            w.startElement( "li" );
354            w.writeMarkup( format( "pluginxdoc.mojodescriptor.since", value ) );
355            w.endElement(); //li
356        }
357
358        value = mojoDescriptor.getPhase();
359        if ( StringUtils.isNotEmpty( value ) )
360        {
361            addedUl = addUl( w, addedUl );
362            w.startElement( "li" );
363            w.writeMarkup( format( "pluginxdoc.mojodescriptor.phase", value ) );
364            w.endElement(); //li
365        }
366
367        value = mojoDescriptor.getExecutePhase();
368        if ( StringUtils.isNotEmpty( value ) )
369        {
370            addedUl = addUl( w, addedUl );
371            w.startElement( "li" );
372            w.writeMarkup( format( "pluginxdoc.mojodescriptor.executePhase", value ) );
373            w.endElement(); //li
374        }
375
376        value = mojoDescriptor.getExecuteGoal();
377        if ( StringUtils.isNotEmpty( value ) )
378        {
379            addedUl = addUl( w, addedUl );
380            w.startElement( "li" );
381            w.writeMarkup( format( "pluginxdoc.mojodescriptor.executeGoal", value ) );
382            w.endElement(); //li
383        }
384
385        value = mojoDescriptor.getExecuteLifecycle();
386        if ( StringUtils.isNotEmpty( value ) )
387        {
388            addedUl = addUl( w, addedUl );
389            w.startElement( "li" );
390            w.writeMarkup( format( "pluginxdoc.mojodescriptor.executeLifecycle", value ) );
391            w.endElement(); //li
392        }
393
394        if ( mojoDescriptor.isOnlineRequired() )
395        {
396            addedUl = addUl( w, addedUl );
397            w.startElement( "li" );
398            w.writeMarkup( getString( "pluginxdoc.mojodescriptor.onlineRequired" ) );
399            w.endElement(); //li
400        }
401
402        if ( !mojoDescriptor.isInheritedByDefault() )
403        {
404            addedUl = addUl( w, addedUl );
405            w.startElement( "li" );
406            w.writeMarkup( getString( "pluginxdoc.mojodescriptor.inheritedByDefault" ) );
407            w.endElement(); //li
408        }
409
410        if ( addedUl )
411        {
412            w.endElement(); //ul
413        }
414    }
415
416    /**
417     * @param mojoDescriptor not null
418     * @param w              not null
419     */
420    private void writeGoalParameterTable( MojoDescriptor mojoDescriptor, XMLWriter w )
421    {
422        List<Parameter> parameterList = mojoDescriptor.getParameters();
423
424        // remove components and read-only parameters
425        List<Parameter> list = filterParameters( parameterList );
426
427        if ( !list.isEmpty() )
428        {
429            writeParameterSummary( list, w, mojoDescriptor.getGoal() );
430            writeParameterDetails( list, w, mojoDescriptor.getGoal() );
431        }
432        else
433        {
434            w.startElement( "subsection" );
435            w.addAttribute( "name", getString( "pluginxdoc.mojodescriptor.parameters" ) );
436
437            w.startElement( "p" );
438            w.writeMarkup( getString( "pluginxdoc.mojodescriptor.noParameter" ) );
439            w.endElement(); //p
440
441            w.endElement();
442        }
443    }
444
445    /**
446     * Filter parameters to only retain those which must be documented, i.e. neither components nor readonly.
447     *
448     * @param parameterList not null
449     * @return the parameters list without components.
450     */
451    private List<Parameter> filterParameters( List<Parameter> parameterList )
452    {
453        List<Parameter> filtered = new ArrayList<>();
454
455        if ( parameterList != null )
456        {
457            for ( Parameter parameter : parameterList )
458            {
459                if ( parameter.isEditable() )
460                {
461                    String expression = parameter.getExpression();
462
463                    if ( expression == null || !expression.startsWith( "${component." ) )
464                    {
465                        filtered.add( parameter );
466                    }
467                }
468            }
469        }
470
471        return filtered;
472    }
473
474    /**
475     * @param parameterList  not null
476     * @param w              not null
477     */
478    private void writeParameterDetails( List<Parameter> parameterList, XMLWriter w, String goal )
479    {
480        w.startElement( "subsection" );
481        w.addAttribute( "name", getString( "pluginxdoc.mojodescriptor.parameter.details" ) );
482
483        for ( Iterator<Parameter> parameters = parameterList.iterator(); parameters.hasNext(); )
484        {
485            Parameter parameter = parameters.next();
486
487            w.startElement( "h4" );
488            w.writeMarkup( format( "pluginxdoc.mojodescriptor.parameter.name_internal", parameter.getName() ) );
489            w.endElement();
490
491            String context = "Parameter " + parameter.getName() + " in goal " + goal;
492            if ( StringUtils.isNotEmpty( parameter.getDeprecated() ) )
493            {
494                w.startElement( "div" );
495                String deprecated = getXhtmlWithValidatedLinks( parameter.getDeprecated(), context );
496                w.writeMarkup( format( "pluginxdoc.mojodescriptor.parameter.deprecated", deprecated ) );
497                w.endElement(); // div
498            }
499
500            w.startElement( "div" );
501            if ( StringUtils.isNotEmpty( parameter.getDescription() ) )
502            {
503                
504                w.writeMarkup( getXhtmlWithValidatedLinks( parameter.getDescription(), context ) );
505            }
506            else
507            {
508                w.writeMarkup( getString( "pluginxdoc.nodescription" ) );
509            }
510            w.endElement(); // div
511
512            boolean addedUl = false;
513            addedUl = addUl( w, addedUl, parameter.getType() );
514            String typeValue = getLinkedType( parameter, false );
515            writeDetail( getString( "pluginxdoc.mojodescriptor.parameter.type" ), typeValue, w );
516
517            if ( StringUtils.isNotEmpty( parameter.getSince() ) )
518            {
519                addedUl = addUl( w, addedUl );
520                writeDetail( getString( "pluginxdoc.mojodescriptor.parameter.since" ), parameter.getSince(), w );
521            }
522
523            if ( parameter.isRequired() )
524            {
525                addedUl = addUl( w, addedUl );
526                writeDetail( getString( "pluginxdoc.mojodescriptor.parameter.required" ), getString( "pluginxdoc.yes" ),
527                             w );
528            }
529            else
530            {
531                addedUl = addUl( w, addedUl );
532                writeDetail( getString( "pluginxdoc.mojodescriptor.parameter.required" ), getString( "pluginxdoc.no" ),
533                             w );
534            }
535
536            String expression = parameter.getExpression();
537            addedUl = addUl( w, addedUl, expression );
538            String property = getPropertyFromExpression( expression );
539            if ( property == null )
540            {
541                writeDetail( getString( "pluginxdoc.mojodescriptor.parameter.expression" ), expression, w );
542            }
543            else
544            {
545                writeDetail( getString( "pluginxdoc.mojodescriptor.parameter.property" ), property, w );
546            }
547
548            addedUl = addUl( w, addedUl, parameter.getDefaultValue() );
549            writeDetail( getString( "pluginxdoc.mojodescriptor.parameter.default" ),
550                         escapeXml( parameter.getDefaultValue() ), w );
551
552            addedUl = addUl( w, addedUl, parameter.getAlias() );
553            writeDetail( getString( "pluginxdoc.mojodescriptor.parameter.alias" ), escapeXml( parameter.getAlias() ),
554                         w );
555
556            if ( addedUl )
557            {
558                w.endElement(); //ul
559            }
560
561            if ( parameters.hasNext() )
562            {
563                w.writeMarkup( "<hr/>" );
564            }
565        }
566
567        w.endElement();
568    }
569
570    static String getShortType( String type )
571    {
572        // split into type arguments and main type
573        int startTypeArguments = type.indexOf( '<' );
574        if ( startTypeArguments == -1 )
575        {
576            return getShortTypeOfSimpleType( type );
577        }
578        else
579        {
580            StringBuilder shortType = new StringBuilder();
581            shortType.append( getShortTypeOfSimpleType( type.substring( 0, startTypeArguments ) ) );
582            shortType.append( "<" )
583                .append( getShortTypeOfTypeArgument( 
584                        type.substring( startTypeArguments + 1, type.lastIndexOf( ">" ) ) ) )
585                .append( ">" );
586            return shortType.toString();
587        }
588        
589    }
590
591    private static String getShortTypeOfTypeArgument( String type )
592    {
593        String[] typeArguments = type.split( ",\\s*" );
594        StringBuilder shortType = new StringBuilder();
595        for ( int i = 0; i < typeArguments.length; i++ )
596        {
597            String typeArgument = typeArguments[i];
598            if ( typeArgument.contains( "<" ) )
599            {
600                // nested type arguments lead to ellipsis
601                return "...";
602            }
603            else
604            {
605                shortType.append( getShortTypeOfSimpleType( typeArgument ) );
606                if ( i < typeArguments.length - 1 )
607                {
608                    shortType.append( "," );
609                }
610            }
611        }
612        return shortType.toString();
613    }
614
615    private static String getShortTypeOfSimpleType( String type )
616    {
617        int index = type.lastIndexOf( '.' );
618        return type.substring( index + 1 );
619    }
620
621    private String getLinkedType( Parameter parameter, boolean isShortType  )
622    {
623        final String typeValue;
624        if ( isShortType )
625        {
626            typeValue = getShortType( parameter.getType() );
627        }
628        else
629        {
630            typeValue = parameter.getType();
631        }
632        if ( parameter instanceof EnhancedParameterWrapper )
633        {
634            EnhancedParameterWrapper enhancedParameter = (EnhancedParameterWrapper) parameter;
635            if ( enhancedParameter.getTypeJavadocUrl() != null )
636            {
637                URI javadocUrl = enhancedParameter.getTypeJavadocUrl();
638                // optionally check if link is valid
639                if ( javadocUrl.isAbsolute() 
640                     || disableInternalJavadocLinkValidation 
641                     || JavadocLinkGenerator.isLinkValid( javadocUrl, reportOutputDirectory.toPath() ) )
642                {
643                    return format( "pluginxdoc.mojodescriptor.parameter.type_link",
644                                   new Object[] { escapeXml( typeValue ), enhancedParameter.getTypeJavadocUrl() } );
645                }
646            }
647        }
648        return escapeXml( typeValue );
649    }
650
651    private boolean addUl( XMLWriter w, boolean addedUl, String content )
652    {
653        if ( StringUtils.isNotEmpty( content ) )
654        {
655            return addUl( w, addedUl );
656        }
657        return addedUl;
658    }
659
660    private boolean addUl( XMLWriter w, boolean addedUl )
661    {
662        if ( !addedUl )
663        {
664            w.startElement( "ul" );
665            addedUl = true;
666        }
667        return addedUl;
668    }
669
670    private String getPropertyFromExpression( String expression )
671    {
672        if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${" ) && expression.endsWith( "}" )
673            && !expression.substring( 2 ).contains( "${" ) )
674        {
675            // expression="${xxx}" -> property="xxx"
676            return expression.substring( 2, expression.length() - 1 );
677        }
678        // no property can be extracted
679        return null;
680    }
681
682    /**
683     * @param param not null
684     * @param value could be null
685     * @param w     not null
686     */
687    private void writeDetail( String param, String value, XMLWriter w )
688    {
689        if ( StringUtils.isNotEmpty( value ) )
690        {
691            w.startElement( "li" );
692            w.writeMarkup( format( "pluginxdoc.detail", new String[]{ param, value } ) );
693            w.endElement(); //li
694        }
695    }
696
697    /**
698     * @param parameterList  not null
699     * @param w              not null
700     */
701    private void writeParameterSummary( List<Parameter> parameterList, XMLWriter w, String goal )
702    {
703        List<Parameter> requiredParams = getParametersByRequired( true, parameterList );
704        if ( !requiredParams.isEmpty() )
705        {
706            writeParameterList( getString( "pluginxdoc.mojodescriptor.requiredParameters" ),
707                                requiredParams, w, goal );
708        }
709
710        List<Parameter> optionalParams = getParametersByRequired( false, parameterList );
711        if ( !optionalParams.isEmpty() )
712        {
713            writeParameterList( getString( "pluginxdoc.mojodescriptor.optionalParameters" ),
714                                optionalParams, w, goal );
715        }
716    }
717
718    /**
719     * @param title          not null
720     * @param parameterList  not null
721     * @param w              not null
722     */
723    private void writeParameterList( String title, List<Parameter> parameterList, XMLWriter w, String goal )
724    {
725        w.startElement( "subsection" );
726        w.addAttribute( "name", title );
727
728        w.startElement( "table" );
729        w.addAttribute( "border", "0" );
730
731        w.startElement( "tr" );
732        w.startElement( "th" );
733        w.writeText( getString( "pluginxdoc.mojodescriptor.parameter.name" ) );
734        w.endElement(); //th
735        w.startElement( "th" );
736        w.writeText( getString( "pluginxdoc.mojodescriptor.parameter.type" ) );
737        w.endElement(); //th
738        w.startElement( "th" );
739        w.writeText( getString( "pluginxdoc.mojodescriptor.parameter.since" ) );
740        w.endElement(); //th
741        w.startElement( "th" );
742        w.writeText( getString( "pluginxdoc.mojodescriptor.parameter.description" ) );
743        w.endElement(); //th
744        w.endElement(); //tr
745
746        for ( Parameter parameter : parameterList )
747        {
748            w.startElement( "tr" );
749
750            // name
751            w.startElement( "td" );
752            w.writeMarkup( format( "pluginxdoc.mojodescriptor.parameter.name_link", parameter.getName() ) );
753            w.endElement(); //td
754
755            //type
756            w.startElement( "td" );
757            w.writeMarkup( "<code>" + getLinkedType( parameter, true ) + "</code>" );
758            w.endElement(); //td
759
760            // since
761            w.startElement( "td" );
762            if ( StringUtils.isNotEmpty( parameter.getSince() ) )
763            {
764                w.writeMarkup( "<code>" + parameter.getSince() + "</code>" );
765            }
766            else
767            {
768                w.writeMarkup( "<code>-</code>" );
769            }
770            w.endElement(); //td
771
772            // description
773            w.startElement( "td" );
774            String description;
775            String context = "Parameter " + parameter.getName() + " in goal " + goal;
776            if ( StringUtils.isNotEmpty( parameter.getDeprecated() ) )
777            {
778                String deprecated = getXhtmlWithValidatedLinks( parameter.getDescription(), context );
779                description = format( "pluginxdoc.mojodescriptor.parameter.deprecated", deprecated );
780            }
781            else if ( StringUtils.isNotEmpty( parameter.getDescription() ) )
782            {
783                description = getXhtmlWithValidatedLinks( parameter.getDescription(), context );
784            }
785            else
786            {
787                description = getString( "pluginxdoc.nodescription" );
788            }
789            w.writeMarkup( description + "<br/>" );
790
791            if ( StringUtils.isNotEmpty( parameter.getDefaultValue() ) )
792            {
793                w.writeMarkup( format( "pluginxdoc.mojodescriptor.parameter.defaultValue",
794                                       escapeXml( parameter.getDefaultValue() ) ) );
795                w.writeMarkup( "<br/>" );
796            }
797
798            String property = getPropertyFromExpression( parameter.getExpression() );
799            if ( property != null )
800            {
801                w.writeMarkup( format( "pluginxdoc.mojodescriptor.parameter.property.description", property ) );
802                w.writeMarkup( "<br/>" );
803            }
804
805            if ( StringUtils.isNotEmpty( parameter.getAlias() ) )
806            {
807                w.writeMarkup( format( "pluginxdoc.mojodescriptor.parameter.alias.description",
808                                       escapeXml( parameter.getAlias() ) ) );
809            }
810
811            w.endElement(); //td
812            w.endElement(); //tr
813        }
814
815        w.endElement(); //table
816        w.endElement(); //section
817    }
818
819    /**
820     * @param required      <code>true</code> for required parameters, <code>false</code> otherwise.
821     * @param parameterList not null
822     * @return list of parameters depending the value of <code>required</code>
823     */
824    private List<Parameter> getParametersByRequired( boolean required, List<Parameter> parameterList )
825    {
826        List<Parameter> list = new ArrayList<>();
827
828        for ( Parameter parameter : parameterList )
829        {
830            if ( parameter.isRequired() == required )
831            {
832                list.add( parameter );
833            }
834        }
835
836        return list;
837    }
838
839    /**
840     * Gets the resource bundle for the <code>locale</code> instance variable.
841     *
842     * @return The resource bundle for the <code>locale</code> instance variable.
843     */
844    private ResourceBundle getBundle()
845    {
846        return ResourceBundle.getBundle( "pluginxdoc", locale, getClass().getClassLoader() );
847    }
848
849    /**
850     * @param key not null
851     * @return Localized, text identified by <code>key</code>.
852     * @see #getBundle()
853     */
854    private String getString( String key )
855    {
856        return getBundle().getString( key );
857    }
858
859    /**
860     * Convenience method.
861     *
862     * @param key  not null
863     * @param arg1 not null
864     * @return Localized, formatted text identified by <code>key</code>.
865     * @see #format(String, Object[])
866     */
867    private String format( String key, Object arg1 )
868    {
869        return format( key, new Object[]{ arg1 } );
870    }
871
872    /**
873     * Looks up the value for <code>key</code> in the <code>ResourceBundle</code>,
874     * then formats that value for the specified <code>Locale</code> using <code>args</code>.
875     *
876     * @param key  not null
877     * @param args not null
878     * @return Localized, formatted text identified by <code>key</code>.
879     */
880    private String format( String key, Object[] args )
881    {
882        String pattern = getString( key );
883        // we don't need quoting so spare us the confusion in the resource bundle to double them up in some keys
884        pattern = StringUtils.replace( pattern, "'", "''" );
885
886        MessageFormat messageFormat = new MessageFormat( "" );
887        messageFormat.setLocale( locale );
888        messageFormat.applyPattern( pattern );
889
890        return messageFormat.format( args );
891    }
892
893    /**
894     * @param text the string to escape
895     * @return A string escaped with XML entities
896     */
897    private String escapeXml( String text )
898    {
899        if ( text != null )
900        {
901            text = text.replace( "&", "&amp;" );
902            text = text.replace( "<", "&lt;" );
903            text = text.replace( ">", "&gt;" );
904            text = text.replace( "\"", "&quot;" );
905            text = text.replace( "\'", "&apos;" );
906        }
907        return text;
908    }
909
910    String getXhtmlWithValidatedLinks( String xhtmlText, String context )
911    {
912        if ( disableInternalJavadocLinkValidation )
913        {
914            return xhtmlText;
915        }
916        StringBuffer sanitizedXhtmlText = new StringBuffer();
917        // find all links which are not absolute
918        Matcher matcher = HTML_LINK_PATTERN.matcher( xhtmlText );
919        while ( matcher.find() )
920        {
921            URI link;
922            try
923            {
924                link = new URI( matcher.group( 1 ) );
925                if ( !link.isAbsolute() && !JavadocLinkGenerator.isLinkValid( link, reportOutputDirectory.toPath() ) )
926                {
927                    matcher.appendReplacement( sanitizedXhtmlText, matcher.group( 2 ) );
928                    LOG.debug( "Removed invalid link {} in {}", link, context );
929                }
930                else
931                {
932                    matcher.appendReplacement( sanitizedXhtmlText, matcher.group( 0 ) );
933                }
934            }
935            catch ( URISyntaxException e )
936            {
937                LOG.warn( "Invalid URI {} found in {}. Cannot validate, leave untouched", matcher.group( 1 ), context );
938                matcher.appendReplacement( sanitizedXhtmlText, matcher.group( 0 ) );
939            }
940        }
941        matcher.appendTail( sanitizedXhtmlText );
942        return sanitizedXhtmlText.toString();
943    }
944}