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