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("&", "&"); 785 text = text.replace("<", "<"); 786 text = text.replace(">", ">"); 787 text = text.replace("\"", """); 788 text = text.replace("\'", "'"); 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}