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