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