michael-o commented on code in PR #225:
URL: 
https://github.com/apache/maven-plugin-tools/pull/225#discussion_r1331250634


##########
maven-plugin-report-plugin/src/main/java/org/apache/maven/plugin/plugin/report/AbstractPluginReportRenderer.java:
##########
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugin.plugin.report;
+
+import java.util.Locale;
+
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.reporting.AbstractMavenReportRenderer;
+import org.codehaus.plexus.i18n.I18N;
+
+/** Base class for all reports generated by the plugin report plugin. 
Encapsulates i18n functionality as well as functionality to directly
+ * emit XHTML markup */
+public abstract class AbstractPluginReportRenderer extends 
AbstractMavenReportRenderer {
+
+    private static final String KEY_PREFIX = "report.plugin.";
+
+    private static final String RESOURCE_BASENAME = "plugin-report";
+
+    private final I18N i18n;
+
+    protected final Locale locale;
+
+    protected final MavenProject project;
+
+    protected AbstractPluginReportRenderer(Sink sink, Locale locale, I18N 
i18n, MavenProject project) {
+        super(sink);
+        this.i18n = i18n;
+        this.locale = locale;
+        this.project = project;
+    }
+
+    /** @param key The key.
+     * @return The translated string. */
+    protected String getI18nString(String key) {
+        return i18n.getString(RESOURCE_BASENAME, locale, KEY_PREFIX + key);
+    }
+}

Review Comment:
   It should follow the pattern as in here: 
https://github.com/apache/maven-project-info-reports-plugin/blob/ec0918223c43e3135aa067c9fcdc6c0a2427f24a/src/main/java/org/apache/maven/report/projectinfo/AbstractProjectInfoRenderer.java,
 maybe even with `getI18Nsection()`. This stencil is used throughout all 
reporting plugins now, at least in the `doxia-2.0.0` branch.



##########
maven-plugin-report-plugin/src/main/java/org/apache/maven/plugin/plugin/report/PluginOverviewRenderer.java:
##########
@@ -0,0 +1,392 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugin.plugin.report;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.doxia.markup.Markup;
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.Prerequisites;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.tools.plugin.ExtendedPluginDescriptor;
+import org.apache.maven.tools.plugin.util.PluginUtils;
+import org.codehaus.plexus.i18n.I18N;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+/**
+ * Generates an overview page with the list of goals
+ * and a link to the goal's page.
+ */
+class PluginOverviewRenderer extends AbstractPluginReportRenderer {
+
+    private final List<RequirementsHistory> requirementsHistories;
+
+    private final PluginDescriptor pluginDescriptor;
+
+    private final boolean hasExtensionsToLoad;
+
+    /**
+     * @param sink                  not null
+     * @param i18n                  not null
+     * @param locale                not null
+     * @param project               not null
+     * @param requirementsHistories not null
+     * @param pluginDescriptor      not null
+     */
+    PluginOverviewRenderer(
+            Sink sink,
+            I18N i18n,
+            Locale locale,
+            MavenProject project,
+            List<RequirementsHistory> requirementsHistories,
+            PluginDescriptor pluginDescriptor,
+            boolean hasExtensionsToLoad) {
+        super(sink, locale, i18n, project);
+
+        this.requirementsHistories = requirementsHistories;
+
+        this.pluginDescriptor = pluginDescriptor;
+
+        this.hasExtensionsToLoad = hasExtensionsToLoad;
+    }
+
+    @Override
+    public String getTitle() {
+        return getI18nString("title");
+    }

Review Comment:
   Can be in parent, see previous comment



##########
maven-plugin-report-plugin/src/main/java/org/apache/maven/plugin/plugin/report/GoalRenderer.java:
##########
@@ -0,0 +1,544 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugin.plugin.report;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.MessageFormat;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.doxia.sink.SinkFactory;
+import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet.Semantics;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.Parameter;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.tools.plugin.EnhancedParameterWrapper;
+import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
+import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator;
+import org.apache.maven.tools.plugin.util.PluginUtils;
+import org.codehaus.plexus.i18n.I18N;
+
+public class GoalRenderer extends AbstractPluginReportRenderer {
+
+    /** Regular expression matching an XHTML link with group 1 = link target, 
group 2 = link label. */
+    private static final Pattern HTML_LINK_PATTERN = Pattern.compile("<a 
href=\\\"([^\\\"]*)\\\">(.*?)</a>");
+
+    public static GoalRenderer create(
+            SinkFactory sinkFactory,
+            File outputDirectory,
+            I18N i18n,
+            Locale locale,
+            MavenProject project,
+            MojoDescriptor descriptor,
+            boolean disableInternalJavadocLinkValidation,
+            Log log)
+            throws IOException {
+        String filename = descriptor.getGoal() + "-mojo.html";
+        Sink sink = sinkFactory.createSink(outputDirectory, filename);
+        return new GoalRenderer(
+                sink, i18n, locale, project, descriptor, outputDirectory, 
disableInternalJavadocLinkValidation, log);
+    }

Review Comment:
   With the caller comment this method is obsolete, I think



##########
maven-plugin-report-plugin/src/main/java/org/apache/maven/plugin/plugin/report/PluginReport.java:
##########
@@ -229,403 +195,34 @@ public String getOutputName() {
     }
 
     /**
-     * Generate the mojos documentation, as xdoc files.
+     * Generate the mojos' documentation with the {@link #getSinkFactory()}
      *
      * @param pluginDescriptor not null
      * @param locale           not null
      * @throws MavenReportException if any
+     * @throws IOException
      */
     private void generateMojosDocumentation(PluginDescriptor pluginDescriptor, 
Locale locale)
             throws MavenReportException {
-        try {
-            File outputDir;
-            if (!locale.equals(SiteTool.DEFAULT_LOCALE)) {
-                outputDir = new File(new File(generatedSiteDirectory, 
locale.toString()), "xdoc");
-            } else {
-                outputDir = new File(generatedSiteDirectory, "xdoc");
-            }
-            outputDir.mkdirs();
-
-            PluginXdocGenerator generator = new PluginXdocGenerator(
-                    getProject(), locale, getReportOutputDirectory(), 
disableInternalJavadocLinkValidation);
-            PluginToolsRequest pluginToolsRequest = new 
DefaultPluginToolsRequest(getProject(), pluginDescriptor);
-            generator.execute(outputDir, pluginToolsRequest);
-        } catch (GeneratorException e) {
-            throw new MavenReportException("Error writing plugin 
documentation", e);
-        }
-    }
-
-    /**
-     * Generates an overview page with the list of goals
-     * and a link to the goal's page.
-     */
-    static class PluginOverviewRenderer extends AbstractMavenReportRenderer {
-        private final I18N i18n;
-
-        private final Locale locale;
-
-        private final MavenProject project;
-
-        private final List<RequirementsHistory> requirementsHistories;
-
-        private final PluginDescriptor pluginDescriptor;
-
-        private final boolean hasExtensionsToLoad;
-
-        /**
-         * @param sink                  not null
-         * @param i18n                  not null
-         * @param locale                not null
-         * @param project               not null
-         * @param requirementsHistories not null
-         * @param pluginDescriptor      not null
-         */
-        PluginOverviewRenderer(
-                Sink sink,
-                I18N i18n,
-                Locale locale,
-                MavenProject project,
-                List<RequirementsHistory> requirementsHistories,
-                PluginDescriptor pluginDescriptor,
-                boolean hasExtensionsToLoad) {
-            super(sink);
-
-            this.i18n = i18n;
-
-            this.locale = locale;
-
-            this.project = project;
-
-            this.requirementsHistories = requirementsHistories;
-
-            this.pluginDescriptor = pluginDescriptor;
-
-            this.hasExtensionsToLoad = hasExtensionsToLoad;
-        }
-
-        @Override
-        public String getTitle() {
-            return getI18nString("title");
-        }
-
-        /**
-         * @param key The key.
-         * @return The translated string.
-         */
-        protected String getI18nString(String key) {
-            return i18n.getString("plugin-report", locale, "report.plugin." + 
key);
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        protected void renderBody() {
-            startSection(getTitle());
-
-            if (!(pluginDescriptor.getMojos() != null
-                    && pluginDescriptor.getMojos().size() > 0)) {
-                paragraph(getI18nString("goals.nogoal"));
-                endSection();
-                return;
-            }
-
-            paragraph(getI18nString("goals.intro"));
-
-            boolean hasMavenReport = false;
-            for (MojoDescriptor mojo : pluginDescriptor.getMojos()) {
-                if (GeneratorUtils.isMavenReport(mojo.getImplementation(), 
project)) {
-                    hasMavenReport = true;
-                }
-            }
-
-            startTable();
-
-            String goalColumnName = getI18nString("goals.column.goal");
-            String isMavenReport = getI18nString("goals.column.isMavenReport");
-            String descriptionColumnName = 
getI18nString("goals.column.description");
-            if (hasMavenReport) {
-                tableHeader(new String[] {goalColumnName, isMavenReport, 
descriptionColumnName});
-            } else {
-                tableHeader(new String[] {goalColumnName, 
descriptionColumnName});
-            }
-
-            List<MojoDescriptor> mojos = new ArrayList<>();
-            mojos.addAll(pluginDescriptor.getMojos());
-            PluginUtils.sortMojos(mojos);
-            for (MojoDescriptor mojo : mojos) {
-                String goalName = mojo.getFullGoalName();
-
-                /*
-                 * Added ./ to define a relative path
-                 * @see 
AbstractMavenReportRenderer#getValidHref(java.lang.String)
-                 */
-                String goalDocumentationLink = "./" + mojo.getGoal() + 
"-mojo.html";
-
-                String description;
-                if (StringUtils.isNotEmpty(mojo.getDeprecated())) {
-                    description = "<strong>" + 
getI18nString("goal.deprecated") + "</strong> " + mojo.getDeprecated();
-                } else if (StringUtils.isNotEmpty(mojo.getDescription())) {
-                    description = mojo.getDescription();
-                } else {
-                    description = getI18nString("goal.nodescription");
-                }
-
-                sink.tableRow();
-                tableCell(createLinkPatternedText(goalName, 
goalDocumentationLink));
-                if (hasMavenReport) {
-                    if (GeneratorUtils.isMavenReport(mojo.getImplementation(), 
project)) {
-                        tableCell(getI18nString("isReport"));
-                    } else {
-                        tableCell(getI18nString("isNotReport"));
-                    }
-                }
-                tableCell(description, true);
-                sink.tableRow_();
-            }
-
-            endTable();
-
-            startSection(getI18nString("systemrequirements"));
 
-            paragraph(getI18nString("systemrequirements.intro"));
-
-            startTable();
-
-            String maven = discoverMavenRequirement(project, pluginDescriptor);
-            sink.tableRow();
-            tableCell(getI18nString("systemrequirements.maven"));
-            tableCell((maven != null ? maven : 
getI18nString("systemrequirements.nominimum")));
-            sink.tableRow_();
-
-            String jdk = discoverJdkRequirement(project, pluginDescriptor);
-            sink.tableRow();
-            tableCell(getI18nString("systemrequirements.jdk"));
-            tableCell((jdk != null ? jdk : 
getI18nString("systemrequirements.nominimum")));
-            sink.tableRow_();
-
-            endTable();
-
-            endSection();
-
-            renderRequirementsHistories();
-
-            renderUsageSection(hasMavenReport);
-
-            endSection();
-        }
-
-        private void renderRequirementsHistories() {
-            if (requirementsHistories.isEmpty()) {
-                return;
-            }
-
-            startSection(getI18nString("systemrequirements.history"));
-            paragraph(getI18nString("systemrequirements.history.intro"));
-
-            startTable();
-            tableHeader(new String[] {
-                getI18nString("systemrequirements.history.version"),
-                getI18nString("systemrequirements.history.maven"),
-                getI18nString("systemrequirements.history.jdk")
-            });
-
-            requirementsHistories.forEach(requirementsHistory -> {
-                sink.tableRow();
-                tableCell(requirementsHistory.getVersion());
-                tableCell(requirementsHistory.getMaven());
-                tableCell(requirementsHistory.getJdk());
-                sink.tableRow_();
-            });
-            endTable();
-
-            endSection();
-        }
-
-        /**
-         * Render the section about the usage of the plugin.
-         *
-         * @param hasMavenReport If the plugin has a report or not
-         */
-        private void renderUsageSection(boolean hasMavenReport) {
-            startSection(getI18nString("usage"));
-
-            // Configuration
-            paragraph(getI18nString("usage.intro"));
-
-            StringBuilder sb = new StringBuilder();
-            sb.append("<project>").append(Markup.EOL);
-            sb.append("  ...").append(Markup.EOL);
-            sb.append("  <build>").append(Markup.EOL);
-            sb.append("    <!-- " + getI18nString("usage.pluginManagement") + 
" -->")
-                    .append(Markup.EOL);
-            sb.append("    <pluginManagement>").append(Markup.EOL);
-            sb.append("      <plugins>").append(Markup.EOL);
-            sb.append("        <plugin>").append(Markup.EOL);
-            sb.append("          <groupId>")
-                    .append(pluginDescriptor.getGroupId())
-                    .append("</groupId>")
-                    .append(Markup.EOL);
-            sb.append("          <artifactId>")
-                    .append(pluginDescriptor.getArtifactId())
-                    .append("</artifactId>")
-                    .append(Markup.EOL);
-            sb.append("          <version>")
-                    .append(pluginDescriptor.getVersion())
-                    .append("</version>")
-                    .append(Markup.EOL);
-            if (hasExtensionsToLoad) {
-                sb.append("          
<extensions>true</extensions>").append(Markup.EOL);
-            }
-            sb.append("        </plugin>").append(Markup.EOL);
-            sb.append("        ...").append(Markup.EOL);
-            sb.append("      </plugins>").append(Markup.EOL);
-            sb.append("    </pluginManagement>").append(Markup.EOL);
-            sb.append("    <!-- " + getI18nString("usage.plugins") + " 
-->").append(Markup.EOL);
-            sb.append("    <plugins>").append(Markup.EOL);
-            sb.append("      <plugin>").append(Markup.EOL);
-            sb.append("        <groupId>")
-                    .append(pluginDescriptor.getGroupId())
-                    .append("</groupId>")
-                    .append(Markup.EOL);
-            sb.append("        <artifactId>")
-                    .append(pluginDescriptor.getArtifactId())
-                    .append("</artifactId>")
-                    .append(Markup.EOL);
-            sb.append("      </plugin>").append(Markup.EOL);
-            sb.append("      ...").append(Markup.EOL);
-            sb.append("    </plugins>").append(Markup.EOL);
-            sb.append("  </build>").append(Markup.EOL);
-
-            if (hasMavenReport) {
-                sb.append("  ...").append(Markup.EOL);
-                sb.append("  <!-- " + getI18nString("usage.reporting") + " 
-->").append(Markup.EOL);
-                sb.append("  <reporting>").append(Markup.EOL);
-                sb.append("    <plugins>").append(Markup.EOL);
-                sb.append("      <plugin>").append(Markup.EOL);
-                sb.append("        <groupId>")
-                        .append(pluginDescriptor.getGroupId())
-                        .append("</groupId>")
-                        .append(Markup.EOL);
-                sb.append("        <artifactId>")
-                        .append(pluginDescriptor.getArtifactId())
-                        .append("</artifactId>")
-                        .append(Markup.EOL);
-                sb.append("        <version>")
-                        .append(pluginDescriptor.getVersion())
-                        .append("</version>")
-                        .append(Markup.EOL);
-                sb.append("      </plugin>").append(Markup.EOL);
-                sb.append("      ...").append(Markup.EOL);
-                sb.append("    </plugins>").append(Markup.EOL);
-                sb.append("  </reporting>").append(Markup.EOL);
-            }
-
-            sb.append("  ...").append(Markup.EOL);
-            sb.append("</project>");
-
-            verbatimText(sb.toString());
-
-            sink.paragraph();
-            linkPatternedText(getI18nString("configuration.end"));
-            sink.paragraph_();
-
-            endSection();
-        }
-
-        /**
-         * Tries to determine the Maven requirement from either the plugin 
descriptor or (if not set) from the
-         * Maven prerequisites element in the POM.
-         *
-         * @param project      not null
-         * @param pluginDescriptor the plugin descriptor (not null)
-         * @return the Maven version or null if not specified
-         */
-        private static String discoverMavenRequirement(MavenProject project, 
PluginDescriptor pluginDescriptor) {
-            if 
(StringUtils.isNotBlank(pluginDescriptor.getRequiredMavenVersion())) {
-                return pluginDescriptor.getRequiredMavenVersion();
-            }
-            return Optional.ofNullable(project.getPrerequisites())
-                    .map(Prerequisites::getMaven)
-                    .orElse(null);
-        }
-
-        /**
-         * Tries to determine the JDK requirement from the following sources 
(until one is found)
-         * <ol>
-         * <li>use JDK requirement from plugin descriptor</li>
-         * <li>use {@code release} configuration of {@code 
org.apache.maven.plugins:maven-compiler-plugin}</li>
-         * <li>use {@code maven.compiler.release<} property</li>
-         * <li>use {@code target} configuration of {@code 
org.apache.maven.plugins:maven-compiler-plugin}</li>
-         * <li>use {@code maven.compiler.target} property</li>
-         * </ol>
-         *
-         * @param project      not null
-         * @param pluginDescriptor the plugin descriptor (not null)
-         * @return the JDK version
-         */
-        private static String discoverJdkRequirement(MavenProject project, 
PluginDescriptor pluginDescriptor) {
-            String jdk = null;
-            if (pluginDescriptor instanceof ExtendedPluginDescriptor) {
-                ExtendedPluginDescriptor extPluginDescriptor = 
(ExtendedPluginDescriptor) pluginDescriptor;
-                jdk = extPluginDescriptor.getRequiredJavaVersion();
-            }
-            if (jdk != null) {
-                return jdk;
-            }
-            Plugin compiler = 
getCompilerPlugin(project.getBuild().getPluginsAsMap());
-            if (compiler == null) {
-                compiler = 
getCompilerPlugin(project.getPluginManagement().getPluginsAsMap());
-            }
-
-            jdk = getPluginParameter(compiler, "release");
-            if (jdk != null) {
-                return jdk;
-            }
-
-            jdk = 
project.getProperties().getProperty("maven.compiler.release");
-            if (jdk != null) {
-                return jdk;
-            }
-
-            jdk = getPluginParameter(compiler, "target");
-            if (jdk != null) {
-                return jdk;
-            }
-
-            // default value
-            jdk = project.getProperties().getProperty("maven.compiler.target");
-            if (jdk != null) {
-                return jdk;
-            }
-
-            String version = (compiler == null) ? null : compiler.getVersion();
-
-            if (version != null) {
-                return "Default target for maven-compiler-plugin version " + 
version;
-            }
-
-            return null;
-        }
-
-        private static Plugin getCompilerPlugin(Map<String, Plugin> 
pluginsAsMap) {
-            return 
pluginsAsMap.get("org.apache.maven.plugins:maven-compiler-plugin");
-        }
-
-        private static String getPluginParameter(Plugin plugin, String 
parameter) {
-            if (plugin != null) {
-                Xpp3Dom pluginConf = (Xpp3Dom) plugin.getConfiguration();
-
-                if (pluginConf != null) {
-                    Xpp3Dom target = pluginConf.getChild(parameter);
-
-                    if (target != null) {
-                        return target.getValue();
-                    }
+        if (pluginDescriptor.getMojos() != null) {
+            for (MojoDescriptor descriptor : pluginDescriptor.getMojos()) {
+                GoalRenderer renderer;
+                try {
+                    renderer = GoalRenderer.create(
+                            getSinkFactory(),
+                            getReportOutputDirectory(),
+                            i18n,

Review Comment:
   Maybe it would be better be create the subsink here, because the renderer 
should receive a ready-to-go sink and not handle low level stuff: 
https://github.com/apache/maven-site-plugin/blob/8d6a9b4bd3313e5824bf4c42e4fe7d7bc2972233/src/it/projects/MSITE-842/project/src/main/java/org/apache/maven/plugins/it/MyReport.java#L72



##########
maven-plugin-report-plugin/src/main/java/org/apache/maven/plugin/plugin/report/PluginOverviewRenderer.java:
##########
@@ -0,0 +1,392 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugin.plugin.report;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.doxia.markup.Markup;
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.Prerequisites;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.tools.plugin.ExtendedPluginDescriptor;
+import org.apache.maven.tools.plugin.util.PluginUtils;
+import org.codehaus.plexus.i18n.I18N;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+/**
+ * Generates an overview page with the list of goals
+ * and a link to the goal's page.
+ */
+class PluginOverviewRenderer extends AbstractPluginReportRenderer {
+
+    private final List<RequirementsHistory> requirementsHistories;
+
+    private final PluginDescriptor pluginDescriptor;
+
+    private final boolean hasExtensionsToLoad;
+
+    /**
+     * @param sink                  not null
+     * @param i18n                  not null
+     * @param locale                not null
+     * @param project               not null
+     * @param requirementsHistories not null
+     * @param pluginDescriptor      not null
+     */
+    PluginOverviewRenderer(
+            Sink sink,
+            I18N i18n,
+            Locale locale,
+            MavenProject project,
+            List<RequirementsHistory> requirementsHistories,
+            PluginDescriptor pluginDescriptor,
+            boolean hasExtensionsToLoad) {
+        super(sink, locale, i18n, project);
+
+        this.requirementsHistories = requirementsHistories;
+
+        this.pluginDescriptor = pluginDescriptor;
+
+        this.hasExtensionsToLoad = hasExtensionsToLoad;
+    }
+
+    @Override
+    public String getTitle() {
+        return getI18nString("title");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void renderBody() {
+        startSection(getTitle());
+
+        if (!(pluginDescriptor.getMojos() != null && 
pluginDescriptor.getMojos().size() > 0)) {
+            paragraph(getI18nString("goals.nogoal"));
+            endSection();
+            return;
+        }
+
+        paragraph(getI18nString("goals.intro"));
+
+        boolean hasMavenReport = false;
+        for (MojoDescriptor mojo : pluginDescriptor.getMojos()) {
+            if (PluginUtils.isMavenReport(mojo.getImplementation(), project)) {
+                hasMavenReport = true;
+            }
+        }
+
+        startTable();
+
+        String goalColumnName = getI18nString("goals.column.goal");
+        String isMavenReport = getI18nString("goals.column.isMavenReport");
+        String descriptionColumnName = 
getI18nString("goals.column.description");
+        if (hasMavenReport) {
+            tableHeader(new String[] {goalColumnName, isMavenReport, 
descriptionColumnName});
+        } else {
+            tableHeader(new String[] {goalColumnName, descriptionColumnName});
+        }
+
+        List<MojoDescriptor> mojos = new ArrayList<>();
+        mojos.addAll(pluginDescriptor.getMojos());
+        PluginUtils.sortMojos(mojos);
+        for (MojoDescriptor mojo : mojos) {
+            String goalName = mojo.getFullGoalName();
+
+            /*
+             * Added ./ to define a relative path
+             * @see AbstractMavenReportRenderer#getValidHref(java.lang.String)
+             */
+            String goalDocumentationLink = "./" + mojo.getGoal() + 
"-mojo.html";
+
+            String description;
+            if (StringUtils.isNotEmpty(mojo.getDeprecated())) {
+                description = "<strong>" + getI18nString("goal.deprecated") + 
"</strong> " + mojo.getDeprecated();

Review Comment:
   This is a bit ugly and should rather not use then `tableCell()`, but use 
`Sink#tableCell()`/`Sink#tableCell()_`.



##########
maven-plugin-report-plugin/src/main/java/org/apache/maven/plugin/plugin/report/GoalRenderer.java:
##########
@@ -0,0 +1,543 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugin.plugin.report;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.MessageFormat;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.doxia.sink.SinkFactory;
+import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet.Semantics;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.Parameter;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.tools.plugin.EnhancedParameterWrapper;
+import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
+import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator;
+import org.apache.maven.tools.plugin.util.PluginUtils;
+import org.codehaus.plexus.i18n.I18N;
+
+public class GoalRenderer extends AbstractPluginReportRenderer {
+
+    /** Regular expression matching an XHTML link with group 1 = link target, 
group 2 = link label. */
+    private static final Pattern HTML_LINK_PATTERN = Pattern.compile("<a 
href=\\\"([^\\\"]*)\\\">(.*?)</a>");
+
+    public static GoalRenderer create(
+            SinkFactory sinkFactory,
+            File outputDirectory,
+            I18N i18n,
+            Locale locale,
+            MavenProject project,
+            MojoDescriptor descriptor,
+            boolean disableInternalJavadocLinkValidation,
+            Log log)
+            throws IOException {
+        String filename = descriptor.getGoal() + "-mojo.html";
+        Sink sink = sinkFactory.createSink(outputDirectory, filename);
+        return new GoalRenderer(
+                sink, i18n, locale, project, descriptor, outputDirectory, 
disableInternalJavadocLinkValidation, log);
+    }
+
+    /** The directory where the generated site is written. Used for resolving 
relative links to javadoc. */
+    private final File reportOutputDirectory;
+
+    private final MojoDescriptor descriptor;
+    private final boolean disableInternalJavadocLinkValidation;
+
+    private final Log log;
+
+    // only used from tests directly
+    GoalRenderer(
+            Sink sink,
+            I18N i18n,
+            Locale locale,
+            MavenProject project,
+            MojoDescriptor descriptor,
+            File reportOutputDirectory,
+            boolean disableInternalJavadocLinkValidation,
+            Log log) {
+        super(sink, locale, i18n, project);
+        this.reportOutputDirectory = reportOutputDirectory;
+        this.descriptor = descriptor;
+        this.disableInternalJavadocLinkValidation = 
disableInternalJavadocLinkValidation;
+        this.log = log;
+    }
+
+    @Override
+    public String getTitle() {
+        return descriptor.getFullGoalName();
+    }
+
+    @Override
+    protected void renderBody() {
+        startSection(descriptor.getFullGoalName());
+        renderReportNotice();
+        renderDescription(
+                "goal.fullname", descriptor.getPluginDescriptor().getId() + 
":" + descriptor.getGoal(), false);
+
+        String context = "goal " + descriptor.getGoal();
+        if (StringUtils.isNotEmpty(descriptor.getDeprecated())) {
+            renderDescription("goal.deprecated", 
getXhtmlWithValidatedLinks(descriptor.getDeprecated(), context), true);
+        }
+        if (StringUtils.isNotEmpty(descriptor.getDescription())) {
+            renderDescription(
+                    "goal.description", 
getXhtmlWithValidatedLinks(descriptor.getDescription(), context), true);
+        } else {
+            renderDescription("goal.description", 
getI18nString("goal.nodescription"), false);
+        }
+        renderAttributes();
+
+        List<Parameter> parameterList = filterParameters(
+                descriptor.getParameters() != null ? 
descriptor.getParameters() : Collections.emptyList());
+        if (parameterList.isEmpty()) {
+            startSection(getI18nString("goal.parameters"));
+            sink.paragraph();
+            sink.text(getI18nString("goal.noParameter"));
+            sink.paragraph_();
+            endSection();
+        } else {
+            renderParameterOverviewTable(
+                    getI18nString("goal.requiredParameters"),
+                    
parameterList.stream().filter(Parameter::isRequired).iterator());
+            renderParameterOverviewTable(
+                    getI18nString("goal.optionalParameters"),
+                    parameterList.stream().filter(p -> 
!p.isRequired()).iterator());
+            renderParameterDetails(parameterList.iterator());
+        }
+        endSection();
+    }
+
+    /** Filter parameters to only retain those which must be documented, i.e. 
neither components nor read-only ones.
+     *
+     * @param parameterList not null
+     * @return the parameters list without components. */
+    private static List<Parameter> filterParameters(Collection<Parameter> 
parameterList) {
+        return parameterList.stream()
+                .filter(p -> p.isEditable()
+                        && (p.getExpression() == null || 
!p.getExpression().startsWith("${component.")))
+                .collect(Collectors.toList());
+    }
+
+    private void renderReportNotice() {
+        if (PluginUtils.isMavenReport(descriptor.getImplementation(), 
project)) {
+            renderDescription("goal.notice.prefix", 
getI18nString("goal.notice.isMavenReport"), false);
+        }
+    }
+
+    /**
+     * A description consists of a term/prefix and the actual description text
+     */
+    private void renderDescription(String prefixKey, String description, 
boolean isHtmlMarkup) {
+        // TODO: convert to dt and dd elements
+        renderDescriptionPrefix(prefixKey);
+        sink.paragraph();
+        if (isHtmlMarkup) {
+            sink.rawText(description);
+        } else {
+            sink.text(description);
+        }
+        sink.paragraph_(); // p
+    }
+
+    private void renderDescriptionPrefix(String prefixKey) {
+        sink.paragraph();
+        sink.inline(Semantics.STRONG);
+        sink.text(getI18nString(prefixKey));
+        sink.inline_();
+        sink.text(":");
+        sink.paragraph_();
+    }
+
+    @SuppressWarnings("deprecation")
+    private void renderAttributes() {
+        renderDescriptionPrefix("goal.attributes");
+        sink.list();
+
+        renderAttribute(descriptor.isProjectRequired(), 
"goal.projectRequired");
+        renderAttribute(descriptor.isRequiresReports(), "goal.reportingMojo");
+        renderAttribute(descriptor.isAggregator(), "goal.aggregator");
+        renderAttribute(descriptor.isDirectInvocationOnly(), 
"goal.directInvocationOnly");
+        renderAttribute(descriptor.isDependencyResolutionRequired(), 
"goal.dependencyResolutionRequired");
+
+        if (descriptor instanceof ExtendedMojoDescriptor) {
+            ExtendedMojoDescriptor extendedDescriptor = 
(ExtendedMojoDescriptor) descriptor;
+            
renderAttribute(extendedDescriptor.getDependencyCollectionRequired(), 
"goal.dependencyCollectionRequired");
+        }
+
+        renderAttribute(descriptor.isThreadSafe(), "goal.threadSafe");
+        renderAttribute(!descriptor.isThreadSafe(), "goal.notThreadSafe");
+        renderAttribute(descriptor.getSince(), "goal.since");
+        renderAttribute(descriptor.getPhase(), "goal.phase");
+        renderAttribute(descriptor.getExecutePhase(), "goal.executePhase");
+        renderAttribute(descriptor.getExecuteGoal(), "goal.executeGoal");
+        renderAttribute(descriptor.getExecuteLifecycle(), 
"goal.executeLifecycle");
+        renderAttribute(descriptor.isOnlineRequired(), "goal.onlineRequired");
+        renderAttribute(!descriptor.isInheritedByDefault(), 
"goal.notInheritedByDefault");
+
+        sink.list_();
+    }
+
+    private void renderAttribute(boolean condition, String attributeKey) {
+        renderAttribute(condition, attributeKey, Optional.empty());
+    }
+
+    private void renderAttribute(String conditionAndCodeArgument, String 
attributeKey) {
+        renderAttribute(
+                StringUtils.isNotEmpty(conditionAndCodeArgument),
+                attributeKey,
+                Optional.ofNullable(conditionAndCodeArgument));
+    }
+
+    private void renderAttribute(boolean condition, String attributeKey, 
Optional<String> codeArgument) {
+        if (condition) {
+            sink.listItem();
+            linkPatternedText(getI18nString(attributeKey));
+            if (codeArgument.isPresent()) {
+                text(": ");
+                sink.inline(Semantics.CODE);
+                sink.text(codeArgument.get());
+                sink.inline_();
+            }
+            text(".");
+            sink.listItem_();
+        }
+    }
+
+    private void renderParameterOverviewTable(String title, 
Iterator<Parameter> parameters) {
+        // don't emit empty tables
+        if (!parameters.hasNext()) {
+            return;
+        }
+        startSection(title);
+        startTable();
+        tableHeader(new String[] {
+            getI18nString("goal.parameter.name.header"),
+            getI18nString("goal.parameter.type.header"),
+            getI18nString("goal.parameter.since.header"),
+            getI18nString("goal.parameter.description.header")
+        });
+        while (parameters.hasNext()) {
+            renderParameterOverviewTableRow(parameters.next());
+        }
+        endTable();
+        endSection();
+    }
+
+    private void renderTableCellWithCode(String text) {
+        renderTableCellWithCode(text, Optional.empty());
+    }
+
+    private void renderTableCellWithCode(String text, URI link) {
+        renderTableCellWithCode(text, Optional.of(link));
+    }
+
+    private void renderTableCellWithCode(String text, Optional<URI> link) {
+        sink.tableCell();
+        sink.inline(Semantics.CODE);
+        if (link.isPresent()) {
+            sink.link(link.get().toString(), null);
+        }
+        sink.text(text);
+        if (link.isPresent()) {
+            sink.link_();
+        }
+        sink.inline_();
+        sink.tableCell_();
+    }
+
+    private void renderParameterOverviewTableRow(Parameter parameter) {
+        sink.tableRow();
+        // name
+        try {
+            renderTableCellWithCode(
+                    format("goal.parameter.name", parameter.getName()), new 
URI(null, null, parameter.getName()));
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException("Cannot create fragment link 
from " + parameter.getName(), e);
+        }
+
+        // type
+        Map.Entry<String, Optional<URI>> type = getLinkedType(parameter, true);
+        renderTableCellWithCode(type.getKey(), type.getValue());
+
+        // since
+        String since = StringUtils.defaultIfEmpty(parameter.getSince(), "-");
+        renderTableCellWithCode(since);
+
+        // description
+        sink.tableCell();
+        String description;
+        String context = "Parameter " + parameter.getName() + " in goal " + 
descriptor.getGoal();
+        if (StringUtils.isNotEmpty(parameter.getDeprecated())) {
+            String deprecated = 
getXhtmlWithValidatedLinks(parameter.getDescription(), context);
+            description = format("goal.parameter.deprecated", deprecated);
+        } else if (StringUtils.isNotEmpty(parameter.getDescription())) {
+            description = 
getXhtmlWithValidatedLinks(parameter.getDescription(), context);
+        } else {
+            description = getI18nString("goal.nodescription");
+        }
+        sink.rawText(description);
+        renderTableCellDetail("goal.parameter.defaultValue", 
parameter.getDefaultValue());
+        renderTableCellDetail("goal.parameter.property", 
getPropertyFromExpression(parameter.getExpression()));
+        renderTableCellDetail("goal.parameter.alias", parameter.getAlias());
+        sink.tableCell_();
+
+        sink.tableRow_();
+    }
+
+    private void renderParameterDetails(Iterator<Parameter> parameters) {
+
+        startSection(getI18nString("goal.parameter.details"));
+
+        while (parameters.hasNext()) {
+            Parameter parameter = parameters.next();
+            // TODO: rework parameter, currently 3 are generated: one from 
skin, one from startSection (all lowercase) and one manually
+            sink.anchor(parameter.getName());

Review Comment:
   I need to think about this and look at the output first.



##########
maven-plugin-report-plugin/src/main/java/org/apache/maven/plugin/plugin/report/PluginOverviewRenderer.java:
##########
@@ -0,0 +1,392 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugin.plugin.report;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.doxia.markup.Markup;
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.Prerequisites;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.tools.plugin.ExtendedPluginDescriptor;
+import org.apache.maven.tools.plugin.util.PluginUtils;
+import org.codehaus.plexus.i18n.I18N;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+/**
+ * Generates an overview page with the list of goals
+ * and a link to the goal's page.
+ */
+class PluginOverviewRenderer extends AbstractPluginReportRenderer {
+
+    private final List<RequirementsHistory> requirementsHistories;
+
+    private final PluginDescriptor pluginDescriptor;
+
+    private final boolean hasExtensionsToLoad;
+
+    /**
+     * @param sink                  not null
+     * @param i18n                  not null
+     * @param locale                not null
+     * @param project               not null
+     * @param requirementsHistories not null
+     * @param pluginDescriptor      not null
+     */
+    PluginOverviewRenderer(
+            Sink sink,
+            I18N i18n,
+            Locale locale,
+            MavenProject project,
+            List<RequirementsHistory> requirementsHistories,
+            PluginDescriptor pluginDescriptor,
+            boolean hasExtensionsToLoad) {
+        super(sink, locale, i18n, project);
+
+        this.requirementsHistories = requirementsHistories;
+
+        this.pluginDescriptor = pluginDescriptor;
+
+        this.hasExtensionsToLoad = hasExtensionsToLoad;
+    }
+
+    @Override
+    public String getTitle() {
+        return getI18nString("title");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void renderBody() {
+        startSection(getTitle());
+
+        if (!(pluginDescriptor.getMojos() != null && 
pluginDescriptor.getMojos().size() > 0)) {
+            paragraph(getI18nString("goals.nogoal"));
+            endSection();
+            return;
+        }
+
+        paragraph(getI18nString("goals.intro"));
+
+        boolean hasMavenReport = false;
+        for (MojoDescriptor mojo : pluginDescriptor.getMojos()) {
+            if (PluginUtils.isMavenReport(mojo.getImplementation(), project)) {
+                hasMavenReport = true;
+            }
+        }
+
+        startTable();
+
+        String goalColumnName = getI18nString("goals.column.goal");
+        String isMavenReport = getI18nString("goals.column.isMavenReport");
+        String descriptionColumnName = 
getI18nString("goals.column.description");
+        if (hasMavenReport) {
+            tableHeader(new String[] {goalColumnName, isMavenReport, 
descriptionColumnName});
+        } else {
+            tableHeader(new String[] {goalColumnName, descriptionColumnName});
+        }
+
+        List<MojoDescriptor> mojos = new ArrayList<>();
+        mojos.addAll(pluginDescriptor.getMojos());
+        PluginUtils.sortMojos(mojos);
+        for (MojoDescriptor mojo : mojos) {
+            String goalName = mojo.getFullGoalName();
+
+            /*
+             * Added ./ to define a relative path
+             * @see AbstractMavenReportRenderer#getValidHref(java.lang.String)
+             */
+            String goalDocumentationLink = "./" + mojo.getGoal() + 
"-mojo.html";
+
+            String description;
+            if (StringUtils.isNotEmpty(mojo.getDeprecated())) {
+                description = "<strong>" + getI18nString("goal.deprecated") + 
"</strong> " + mojo.getDeprecated();
+            } else if (StringUtils.isNotEmpty(mojo.getDescription())) {
+                description = mojo.getDescription();
+            } else {
+                description = getI18nString("goal.nodescription");
+            }
+
+            sink.tableRow();
+            tableCell(createLinkPatternedText(goalName, 
goalDocumentationLink));
+            if (hasMavenReport) {
+                if (PluginUtils.isMavenReport(mojo.getImplementation(), 
project)) {
+                    tableCell(getI18nString("isReport"));
+                } else {
+                    tableCell(getI18nString("isNotReport"));
+                }
+            }
+            tableCell(description, true);

Review Comment:
    Description handling should be moved here since it appear first here.



##########
maven-plugin-report-plugin/src/main/java/org/apache/maven/plugin/plugin/report/PluginOverviewRenderer.java:
##########
@@ -0,0 +1,392 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugin.plugin.report;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.doxia.markup.Markup;
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.Prerequisites;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.tools.plugin.ExtendedPluginDescriptor;
+import org.apache.maven.tools.plugin.util.PluginUtils;
+import org.codehaus.plexus.i18n.I18N;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+/**
+ * Generates an overview page with the list of goals
+ * and a link to the goal's page.
+ */
+class PluginOverviewRenderer extends AbstractPluginReportRenderer {
+
+    private final List<RequirementsHistory> requirementsHistories;
+
+    private final PluginDescriptor pluginDescriptor;
+
+    private final boolean hasExtensionsToLoad;
+
+    /**
+     * @param sink                  not null
+     * @param i18n                  not null
+     * @param locale                not null
+     * @param project               not null
+     * @param requirementsHistories not null
+     * @param pluginDescriptor      not null
+     */
+    PluginOverviewRenderer(
+            Sink sink,
+            I18N i18n,
+            Locale locale,
+            MavenProject project,
+            List<RequirementsHistory> requirementsHistories,
+            PluginDescriptor pluginDescriptor,
+            boolean hasExtensionsToLoad) {
+        super(sink, locale, i18n, project);
+
+        this.requirementsHistories = requirementsHistories;
+
+        this.pluginDescriptor = pluginDescriptor;
+
+        this.hasExtensionsToLoad = hasExtensionsToLoad;
+    }
+
+    @Override
+    public String getTitle() {
+        return getI18nString("title");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void renderBody() {
+        startSection(getTitle());
+
+        if (!(pluginDescriptor.getMojos() != null && 
pluginDescriptor.getMojos().size() > 0)) {
+            paragraph(getI18nString("goals.nogoal"));
+            endSection();
+            return;
+        }
+
+        paragraph(getI18nString("goals.intro"));
+
+        boolean hasMavenReport = false;
+        for (MojoDescriptor mojo : pluginDescriptor.getMojos()) {
+            if (PluginUtils.isMavenReport(mojo.getImplementation(), project)) {
+                hasMavenReport = true;
+            }
+        }
+
+        startTable();
+
+        String goalColumnName = getI18nString("goals.column.goal");
+        String isMavenReport = getI18nString("goals.column.isMavenReport");
+        String descriptionColumnName = 
getI18nString("goals.column.description");
+        if (hasMavenReport) {
+            tableHeader(new String[] {goalColumnName, isMavenReport, 
descriptionColumnName});
+        } else {
+            tableHeader(new String[] {goalColumnName, descriptionColumnName});
+        }
+
+        List<MojoDescriptor> mojos = new ArrayList<>();
+        mojos.addAll(pluginDescriptor.getMojos());
+        PluginUtils.sortMojos(mojos);
+        for (MojoDescriptor mojo : mojos) {
+            String goalName = mojo.getFullGoalName();
+
+            /*
+             * Added ./ to define a relative path
+             * @see AbstractMavenReportRenderer#getValidHref(java.lang.String)
+             */
+            String goalDocumentationLink = "./" + mojo.getGoal() + 
"-mojo.html";
+
+            String description;
+            if (StringUtils.isNotEmpty(mojo.getDeprecated())) {
+                description = "<strong>" + getI18nString("goal.deprecated") + 
"</strong> " + mojo.getDeprecated();
+            } else if (StringUtils.isNotEmpty(mojo.getDescription())) {
+                description = mojo.getDescription();
+            } else {
+                description = getI18nString("goal.nodescription");
+            }
+
+            sink.tableRow();
+            tableCell(createLinkPatternedText(goalName, 
goalDocumentationLink));
+            if (hasMavenReport) {
+                if (PluginUtils.isMavenReport(mojo.getImplementation(), 
project)) {
+                    tableCell(getI18nString("isReport"));
+                } else {
+                    tableCell(getI18nString("isNotReport"));
+                }
+            }
+            tableCell(description, true);
+            sink.tableRow_();
+        }
+
+        endTable();
+
+        startSection(getI18nString("systemrequirements"));
+
+        paragraph(getI18nString("systemrequirements.intro"));
+
+        startTable();
+
+        String maven = discoverMavenRequirement(project, pluginDescriptor);
+        sink.tableRow();
+        tableCell(getI18nString("systemrequirements.maven"));
+        tableCell((maven != null ? maven : 
getI18nString("systemrequirements.nominimum")));
+        sink.tableRow_();
+
+        String jdk = discoverJdkRequirement(project, pluginDescriptor);
+        sink.tableRow();
+        tableCell(getI18nString("systemrequirements.jdk"));
+        tableCell((jdk != null ? jdk : 
getI18nString("systemrequirements.nominimum")));
+        sink.tableRow_();
+
+        endTable();
+
+        endSection();
+
+        renderRequirementsHistories();
+
+        renderUsageSection(hasMavenReport);
+
+        endSection();
+    }
+
+    private void renderRequirementsHistories() {
+        if (requirementsHistories.isEmpty()) {
+            return;
+        }
+
+        startSection(getI18nString("systemrequirements.history"));
+        paragraph(getI18nString("systemrequirements.history.intro"));
+
+        startTable();
+        tableHeader(new String[] {
+            getI18nString("systemrequirements.history.version"),
+            getI18nString("systemrequirements.history.maven"),
+            getI18nString("systemrequirements.history.jdk")
+        });
+
+        requirementsHistories.forEach(requirementsHistory -> {
+            sink.tableRow();
+            tableCell(requirementsHistory.getVersion());
+            tableCell(requirementsHistory.getMaven());
+            tableCell(requirementsHistory.getJdk());
+            sink.tableRow_();
+        });
+        endTable();
+
+        endSection();
+    }
+
+    /**
+     * Render the section about the usage of the plugin.
+     *
+     * @param hasMavenReport If the plugin has a report or not
+     */
+    private void renderUsageSection(boolean hasMavenReport) {
+        startSection(getI18nString("usage"));
+
+        // Configuration
+        paragraph(getI18nString("usage.intro"));
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("<project>").append(Markup.EOL);
+        sb.append("  ...").append(Markup.EOL);
+        sb.append("  <build>").append(Markup.EOL);
+        sb.append("    <!-- " + getI18nString("usage.pluginManagement") + " 
-->")
+                .append(Markup.EOL);
+        sb.append("    <pluginManagement>").append(Markup.EOL);
+        sb.append("      <plugins>").append(Markup.EOL);
+        sb.append("        <plugin>").append(Markup.EOL);
+        sb.append("          <groupId>")
+                .append(pluginDescriptor.getGroupId())
+                .append("</groupId>")
+                .append(Markup.EOL);
+        sb.append("          <artifactId>")
+                .append(pluginDescriptor.getArtifactId())
+                .append("</artifactId>")
+                .append(Markup.EOL);
+        sb.append("          <version>")
+                .append(pluginDescriptor.getVersion())
+                .append("</version>")
+                .append(Markup.EOL);
+        if (hasExtensionsToLoad) {
+            sb.append("          
<extensions>true</extensions>").append(Markup.EOL);
+        }
+        sb.append("        </plugin>").append(Markup.EOL);
+        sb.append("        ...").append(Markup.EOL);
+        sb.append("      </plugins>").append(Markup.EOL);
+        sb.append("    </pluginManagement>").append(Markup.EOL);
+        sb.append("    <!-- " + getI18nString("usage.plugins") + " 
-->").append(Markup.EOL);
+        sb.append("    <plugins>").append(Markup.EOL);
+        sb.append("      <plugin>").append(Markup.EOL);
+        sb.append("        <groupId>")
+                .append(pluginDescriptor.getGroupId())
+                .append("</groupId>")
+                .append(Markup.EOL);
+        sb.append("        <artifactId>")
+                .append(pluginDescriptor.getArtifactId())
+                .append("</artifactId>")
+                .append(Markup.EOL);
+        sb.append("      </plugin>").append(Markup.EOL);
+        sb.append("      ...").append(Markup.EOL);
+        sb.append("    </plugins>").append(Markup.EOL);
+        sb.append("  </build>").append(Markup.EOL);
+
+        if (hasMavenReport) {
+            sb.append("  ...").append(Markup.EOL);
+            sb.append("  <!-- " + getI18nString("usage.reporting") + " 
-->").append(Markup.EOL);
+            sb.append("  <reporting>").append(Markup.EOL);
+            sb.append("    <plugins>").append(Markup.EOL);
+            sb.append("      <plugin>").append(Markup.EOL);
+            sb.append("        <groupId>")
+                    .append(pluginDescriptor.getGroupId())
+                    .append("</groupId>")
+                    .append(Markup.EOL);
+            sb.append("        <artifactId>")
+                    .append(pluginDescriptor.getArtifactId())
+                    .append("</artifactId>")
+                    .append(Markup.EOL);
+            sb.append("        <version>")
+                    .append(pluginDescriptor.getVersion())
+                    .append("</version>")
+                    .append(Markup.EOL);
+            sb.append("      </plugin>").append(Markup.EOL);
+            sb.append("      ...").append(Markup.EOL);
+            sb.append("    </plugins>").append(Markup.EOL);
+            sb.append("  </reporting>").append(Markup.EOL);
+        }
+
+        sb.append("  ...").append(Markup.EOL);
+        sb.append("</project>");
+
+        verbatimText(sb.toString());

Review Comment:
   Please add a `FIXME` for Doxia 2.0.0: Turn to `verbatim source`



##########
maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/util/PluginUtils.java:
##########
@@ -137,4 +145,50 @@ public int compare(Parameter parameter1, Parameter 
parameter2) {
             });
         }
     }
+
+    /**
+     * @param mojoClassName a fully qualified Mojo implementation class name, 
not null
+     * @param project a MavenProject instance, could be null
+     * @return <code>true</code> if the Mojo class implements 
<code>MavenReport</code>,
+     * <code>false</code> otherwise.
+     * @throws IllegalArgumentException if any
+     * @since 3.10.0
+     */
+    public static boolean isMavenReport(String mojoClassName, MavenProject 
project) throws IllegalArgumentException {
+        if (mojoClassName == null) {
+            throw new IllegalArgumentException("mojo implementation should be 
declared");
+        }

Review Comment:
   A new method should use `Objects#notNull()` here and the caller should just 
wrap it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@maven.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to