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