View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.tools.plugin.generator;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.OutputStreamWriter;
24  import java.io.Writer;
25  import java.net.URI;
26  import java.util.LinkedHashMap;
27  import java.util.LinkedHashSet;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import org.apache.maven.plugin.descriptor.MojoDescriptor;
33  import org.apache.maven.plugin.descriptor.Parameter;
34  import org.apache.maven.plugin.descriptor.PluginDescriptor;
35  import org.apache.maven.plugin.descriptor.Requirement;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
38  import org.apache.maven.tools.plugin.ExtendedPluginDescriptor;
39  import org.apache.maven.tools.plugin.PluginToolsRequest;
40  import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator;
41  import org.apache.maven.tools.plugin.util.PluginUtils;
42  import org.codehaus.plexus.util.StringUtils;
43  import org.codehaus.plexus.util.io.CachingOutputStream;
44  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
45  import org.codehaus.plexus.util.xml.XMLWriter;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  import static java.nio.charset.StandardCharsets.UTF_8;
50  
51  /**
52   * Serializes
53   * <ol>
54   * <li>a standard <a href="/ref/current/maven-plugin-api/plugin.html">Maven Plugin Descriptor XML file</a></li>
55   * <li>a descriptor containing a limited set of elements for {@link PluginHelpGenerator}</li>
56   * <li>an enhanced descriptor containing HTML values for some elements (instead of plain text as for the other two)
57   * for {@code org.apache.maven.plugin.plugin.report.GoalRenderer}</li>
58   * </ol>
59   * from a given in-memory descriptor. The in-memory descriptor acting as source is supposed to contain XHTML values
60   * for description elements.
61   *
62   */
63  public class PluginDescriptorFilesGenerator implements Generator {
64      private static final Logger LOG = LoggerFactory.getLogger(PluginDescriptorFilesGenerator.class);
65  
66      /**
67       * The type of the plugin descriptor file
68       */
69      enum DescriptorType {
70          STANDARD,
71          LIMITED_FOR_HELP_MOJO,
72          XHTML
73      }
74  
75      @Override
76      public void execute(File destinationDirectory, PluginToolsRequest request) throws GeneratorException {
77          try {
78              // write standard plugin.xml descriptor
79              File f = new File(destinationDirectory, "plugin.xml");
80              writeDescriptor(f, request, DescriptorType.STANDARD);
81  
82              // write plugin-help.xml help-descriptor (containing only a limited set of attributes)
83              MavenProject mavenProject = request.getProject();
84              f = new File(destinationDirectory, PluginHelpGenerator.getPluginHelpPath(mavenProject));
85              writeDescriptor(f, request, DescriptorType.LIMITED_FOR_HELP_MOJO);
86  
87              // write enhanced plugin-enhanced.xml descriptor (containing some XHTML values)
88              f = getEnhancedDescriptorFilePath(mavenProject);
89              writeDescriptor(f, request, DescriptorType.XHTML);
90          } catch (IOException e) {
91              throw new GeneratorException(e.getMessage(), e);
92          }
93      }
94  
95      public static File getEnhancedDescriptorFilePath(MavenProject project) {
96          return new File(project.getBuild().getDirectory(), "plugin-enhanced.xml");
97      }
98  
99      private String getVersion() {
100         Package p = this.getClass().getPackage();
101         String version = (p == null) ? null : p.getSpecificationVersion();
102         return (version == null) ? "SNAPSHOT" : version;
103     }
104 
105     public void writeDescriptor(File destinationFile, PluginToolsRequest request, DescriptorType type)
106             throws IOException {
107         PluginDescriptor pluginDescriptor = request.getPluginDescriptor();
108 
109         if (!destinationFile.getParentFile().exists()) {
110             destinationFile.getParentFile().mkdirs();
111         }
112 
113         String apiVersion = request.getPluginDescriptor().getRequiredMavenVersion();
114         boolean isV4 = apiVersion != null && apiVersion.startsWith("4.");
115 
116         try (Writer writer = new OutputStreamWriter(new CachingOutputStream(destinationFile), UTF_8)) {
117             XMLWriter w = new PrettyPrintXMLWriter(writer, UTF_8.name(), null);
118 
119             final String additionalInfo;
120             switch (type) {
121                 case LIMITED_FOR_HELP_MOJO:
122                     additionalInfo = " (for help mojo with limited elements)";
123                     break;
124                 case XHTML:
125                     additionalInfo = " (enhanced XHTML version (used for plugin:report))";
126                     break;
127                 default:
128                     additionalInfo = "";
129                     break;
130             }
131             w.writeMarkup("\n<!-- Generated by maven-plugin-tools " + getVersion() + additionalInfo + "-->\n\n");
132 
133             w.startElement("plugin");
134             if (isV4) {
135                 w.addAttribute("xmlns", "http://maven.apache.org/PLUGIN/2.0.0");
136                 w.addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
137                 w.addAttribute(
138                         "xsi:location",
139                         "http://maven.apache.org/PLUGIN/2.0.0 https://maven.apache.org/xsd/plugin-2.0.0.xsd");
140             }
141 
142             GeneratorUtils.element(w, "name", pluginDescriptor.getName());
143 
144             GeneratorUtils.element(w, "description", pluginDescriptor.getDescription());
145 
146             GeneratorUtils.element(w, "groupId", pluginDescriptor.getGroupId());
147 
148             GeneratorUtils.element(w, "artifactId", pluginDescriptor.getArtifactId());
149 
150             GeneratorUtils.element(w, "version", pluginDescriptor.getVersion());
151 
152             GeneratorUtils.element(w, "goalPrefix", pluginDescriptor.getGoalPrefix());
153 
154             if (type != DescriptorType.LIMITED_FOR_HELP_MOJO) {
155                 GeneratorUtils.element(w, "isolatedRealm", String.valueOf(pluginDescriptor.isIsolatedRealm()));
156 
157                 GeneratorUtils.element(
158                         w, "inheritedByDefault", String.valueOf(pluginDescriptor.isInheritedByDefault()));
159 
160                 if (pluginDescriptor instanceof ExtendedPluginDescriptor) {
161                     ExtendedPluginDescriptor extPluginDescriptor = (ExtendedPluginDescriptor) pluginDescriptor;
162                     if (StringUtils.isNotBlank(extPluginDescriptor.getRequiredJavaVersion())) {
163                         GeneratorUtils.element(w, "requiredJavaVersion", extPluginDescriptor.getRequiredJavaVersion());
164                     }
165                 }
166                 if (StringUtils.isNotBlank(pluginDescriptor.getRequiredMavenVersion())) {
167                     GeneratorUtils.element(w, "requiredMavenVersion", pluginDescriptor.getRequiredMavenVersion());
168                 }
169             }
170 
171             w.startElement("mojos");
172 
173             final JavadocLinkGenerator javadocLinkGenerator;
174             if (request.getInternalJavadocBaseUrl() != null
175                     || (request.getExternalJavadocBaseUrls() != null
176                             && !request.getExternalJavadocBaseUrls().isEmpty())) {
177                 javadocLinkGenerator = new JavadocLinkGenerator(
178                         request.getInternalJavadocBaseUrl(),
179                         request.getInternalJavadocVersion(),
180                         request.getExternalJavadocBaseUrls(),
181                         request.getSettings());
182             } else {
183                 javadocLinkGenerator = null;
184             }
185             if (pluginDescriptor.getMojos() != null) {
186                 List<MojoDescriptor> descriptors = pluginDescriptor.getMojos();
187 
188                 PluginUtils.sortMojos(descriptors);
189 
190                 for (MojoDescriptor descriptor : descriptors) {
191                     processMojoDescriptor(descriptor, w, type, javadocLinkGenerator, isV4);
192                 }
193             }
194 
195             w.endElement();
196 
197             if (!isV4 && type != DescriptorType.LIMITED_FOR_HELP_MOJO) {
198                 GeneratorUtils.writeDependencies(w, pluginDescriptor);
199             }
200 
201             w.endElement();
202 
203             writer.flush();
204         }
205     }
206 
207     /**
208      *
209      * @param type
210      * @param containsXhtmlValue
211      * @param text
212      * @return the normalized text value (i.e. potentially converted to XHTML)
213      */
214     private static String getTextValue(DescriptorType type, boolean containsXhtmlValue, String text) {
215         final String xhtmlText;
216         if (!containsXhtmlValue) // text comes from legacy extractor
217         {
218             xhtmlText = GeneratorUtils.makeHtmlValid(text);
219         } else {
220             xhtmlText = text;
221         }
222         if (type != DescriptorType.XHTML) {
223             return new HtmlToPlainTextConverter().convert(text);
224         } else {
225             return xhtmlText;
226         }
227     }
228 
229     @SuppressWarnings("deprecation")
230     protected void processMojoDescriptor(
231             MojoDescriptor mojoDescriptor,
232             XMLWriter w,
233             DescriptorType type,
234             JavadocLinkGenerator javadocLinkGenerator,
235             boolean isV4) {
236         boolean containsXhtmlTextValues = mojoDescriptor instanceof ExtendedMojoDescriptor
237                 && ((ExtendedMojoDescriptor) mojoDescriptor).containsXhtmlTextValues();
238 
239         w.startElement("mojo");
240 
241         // ----------------------------------------------------------------------
242         //
243         // ----------------------------------------------------------------------
244 
245         w.startElement("goal");
246         w.writeText(mojoDescriptor.getGoal());
247         w.endElement();
248 
249         // ----------------------------------------------------------------------
250         //
251         // ----------------------------------------------------------------------
252 
253         String description = mojoDescriptor.getDescription();
254 
255         if (description != null && !description.isEmpty()) {
256             w.startElement("description");
257             w.writeText(getTextValue(type, containsXhtmlTextValues, mojoDescriptor.getDescription()));
258             w.endElement();
259         }
260 
261         // ----------------------------------------------------------------------
262         //
263         // ----------------------------------------------------------------------
264 
265         if (StringUtils.isNotEmpty(mojoDescriptor.getDependencyResolutionRequired())) {
266             GeneratorUtils.element(
267                     w,
268                     isV4 ? "dependencyResolution" : "requiresDependencyResolution",
269                     mojoDescriptor.getDependencyResolutionRequired());
270         }
271 
272         // ----------------------------------------------------------------------
273         //
274         // ----------------------------------------------------------------------
275 
276         GeneratorUtils.element(
277                 w,
278                 isV4 ? "directInvocationOnly" : "requiresDirectInvocation",
279                 String.valueOf(mojoDescriptor.isDirectInvocationOnly()));
280 
281         // ----------------------------------------------------------------------
282         //
283         // ----------------------------------------------------------------------
284 
285         GeneratorUtils.element(
286                 w, isV4 ? "projectRequired" : "requiresProject", String.valueOf(mojoDescriptor.isProjectRequired()));
287 
288         // ----------------------------------------------------------------------
289         //
290         // ----------------------------------------------------------------------
291 
292         if (!isV4) {
293             GeneratorUtils.element(w, "requiresReports", String.valueOf(mojoDescriptor.isRequiresReports()));
294         }
295 
296         // ----------------------------------------------------------------------
297         //
298         // ----------------------------------------------------------------------
299 
300         GeneratorUtils.element(w, "aggregator", String.valueOf(mojoDescriptor.isAggregator()));
301 
302         // ----------------------------------------------------------------------
303         //
304         // ----------------------------------------------------------------------
305 
306         GeneratorUtils.element(
307                 w, isV4 ? "onlineRequired" : "requiresOnline", String.valueOf(mojoDescriptor.isOnlineRequired()));
308 
309         // ----------------------------------------------------------------------
310         //
311         // ----------------------------------------------------------------------
312 
313         GeneratorUtils.element(w, "inheritedByDefault", String.valueOf(mojoDescriptor.isInheritedByDefault()));
314 
315         // ----------------------------------------------------------------------
316         //
317         // ----------------------------------------------------------------------
318 
319         if (StringUtils.isNotEmpty(mojoDescriptor.getPhase())) {
320             GeneratorUtils.element(w, "phase", mojoDescriptor.getPhase());
321         }
322 
323         // ----------------------------------------------------------------------
324         //
325         // ----------------------------------------------------------------------
326 
327         if (StringUtils.isNotEmpty(mojoDescriptor.getExecutePhase())) {
328             GeneratorUtils.element(w, "executePhase", mojoDescriptor.getExecutePhase());
329         }
330 
331         if (StringUtils.isNotEmpty(mojoDescriptor.getExecuteGoal())) {
332             GeneratorUtils.element(w, "executeGoal", mojoDescriptor.getExecuteGoal());
333         }
334 
335         if (StringUtils.isNotEmpty(mojoDescriptor.getExecuteLifecycle())) {
336             GeneratorUtils.element(w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle());
337         }
338 
339         // ----------------------------------------------------------------------
340         //
341         // ----------------------------------------------------------------------
342 
343         w.startElement("implementation");
344         w.writeText(mojoDescriptor.getImplementation());
345         w.endElement();
346 
347         // ----------------------------------------------------------------------
348         //
349         // ----------------------------------------------------------------------
350 
351         w.startElement("language");
352         w.writeText(mojoDescriptor.getLanguage());
353         w.endElement();
354 
355         // ----------------------------------------------------------------------
356         //
357         // ----------------------------------------------------------------------
358 
359         if (StringUtils.isNotEmpty(mojoDescriptor.getComponentConfigurator())) {
360             w.startElement("configurator");
361             w.writeText(mojoDescriptor.getComponentConfigurator());
362             w.endElement();
363         }
364 
365         // ----------------------------------------------------------------------
366         //
367         // ----------------------------------------------------------------------
368 
369         if (!isV4 && StringUtils.isNotEmpty(mojoDescriptor.getComponentComposer())) {
370             w.startElement("composer");
371             w.writeText(mojoDescriptor.getComponentComposer());
372             w.endElement();
373         }
374 
375         // ----------------------------------------------------------------------
376         //
377         // ----------------------------------------------------------------------
378 
379         if (!isV4) {
380             w.startElement("instantiationStrategy");
381             w.writeText(mojoDescriptor.getInstantiationStrategy());
382             w.endElement();
383         }
384 
385         // ----------------------------------------------------------------------
386         // Strategy for handling repeated reference to mojo in
387         // the calculated (decorated, resolved) execution stack
388         // ----------------------------------------------------------------------
389 
390         if (!isV4) {
391             w.startElement("executionStrategy");
392             w.writeText(mojoDescriptor.getExecutionStrategy());
393             w.endElement();
394         }
395 
396         // ----------------------------------------------------------------------
397         //
398         // ----------------------------------------------------------------------
399 
400         if (mojoDescriptor.getSince() != null) {
401             w.startElement("since");
402 
403             if (StringUtils.isEmpty(mojoDescriptor.getSince())) {
404                 w.writeText("No version given");
405             } else {
406                 w.writeText(mojoDescriptor.getSince());
407             }
408 
409             w.endElement();
410         }
411 
412         // ----------------------------------------------------------------------
413         //
414         // ----------------------------------------------------------------------
415 
416         if (mojoDescriptor.getDeprecated() != null) {
417             w.startElement("deprecated");
418 
419             if (StringUtils.isEmpty(mojoDescriptor.getDeprecated())) {
420                 w.writeText("No reason given");
421             } else {
422                 w.writeText(getTextValue(type, containsXhtmlTextValues, mojoDescriptor.getDeprecated()));
423             }
424 
425             w.endElement();
426         }
427 
428         // ----------------------------------------------------------------------
429         // Extended (3.0) descriptor
430         // ----------------------------------------------------------------------
431 
432         if (mojoDescriptor instanceof ExtendedMojoDescriptor) {
433             ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor;
434             if (extendedMojoDescriptor.getDependencyCollectionRequired() != null) {
435                 GeneratorUtils.element(
436                         w,
437                         isV4 ? "dependencyCollection" : "requiresDependencyCollection",
438                         extendedMojoDescriptor.getDependencyCollectionRequired());
439             }
440             if (!isV4) {
441                 GeneratorUtils.element(w, "threadSafe", String.valueOf(extendedMojoDescriptor.isThreadSafe()));
442             }
443         }
444 
445         // ----------------------------------------------------------------------
446         // Parameters
447         // ----------------------------------------------------------------------
448 
449         List<Parameter> parameters = mojoDescriptor.getParameters();
450 
451         w.startElement("parameters");
452 
453         Map<String, Requirement> requirements = new LinkedHashMap<>();
454 
455         Set<Parameter> configuration = new LinkedHashSet<>();
456 
457         if (parameters != null) {
458             if (type == DescriptorType.LIMITED_FOR_HELP_MOJO) {
459                 PluginUtils.sortMojoParameters(parameters);
460             }
461 
462             for (Parameter parameter : parameters) {
463                 String expression = getExpression(parameter);
464 
465                 if ((expression != null && !expression.isEmpty()) && expression.startsWith("${component.")) {
466                     // treat it as a component...a requirement, in other words.
467 
468                     // remove "component." plus expression delimiters
469                     String role = expression.substring("${component.".length(), expression.length() - 1);
470 
471                     String roleHint = null;
472 
473                     int posRoleHintSeparator = role.indexOf('#');
474                     if (posRoleHintSeparator > 0) {
475                         roleHint = role.substring(posRoleHintSeparator + 1);
476 
477                         role = role.substring(0, posRoleHintSeparator);
478                     }
479 
480                     // TODO: remove deprecated expression
481                     requirements.put(parameter.getName(), new Requirement(role, roleHint));
482                 } else if (parameter.getRequirement() != null) {
483                     requirements.put(parameter.getName(), parameter.getRequirement());
484                 }
485                 // don't show readonly parameters in help
486                 else if (type != DescriptorType.LIMITED_FOR_HELP_MOJO || parameter.isEditable()) {
487                     // treat it as a normal parameter.
488 
489                     w.startElement("parameter");
490 
491                     GeneratorUtils.element(w, "name", parameter.getName());
492 
493                     if (parameter.getAlias() != null) {
494                         GeneratorUtils.element(w, "alias", parameter.getAlias());
495                     }
496 
497                     writeParameterType(w, type, javadocLinkGenerator, parameter, mojoDescriptor.getGoal());
498 
499                     if (parameter.getSince() != null) {
500                         w.startElement("since");
501 
502                         if (StringUtils.isEmpty(parameter.getSince())) {
503                             w.writeText("No version given");
504                         } else {
505                             w.writeText(parameter.getSince());
506                         }
507 
508                         w.endElement();
509                     }
510 
511                     if (parameter.getDeprecated() != null) {
512                         if (StringUtils.isEmpty(parameter.getDeprecated())) {
513                             GeneratorUtils.element(w, "deprecated", "No reason given");
514                         } else {
515                             GeneratorUtils.element(
516                                     w,
517                                     "deprecated",
518                                     getTextValue(type, containsXhtmlTextValues, parameter.getDeprecated()));
519                         }
520                     }
521 
522                     if (!isV4 && parameter.getImplementation() != null) {
523                         GeneratorUtils.element(w, "implementation", parameter.getImplementation());
524                     }
525 
526                     GeneratorUtils.element(w, "required", Boolean.toString(parameter.isRequired()));
527 
528                     GeneratorUtils.element(w, "editable", Boolean.toString(parameter.isEditable()));
529 
530                     GeneratorUtils.element(
531                             w, "description", getTextValue(type, containsXhtmlTextValues, parameter.getDescription()));
532 
533                     if (isV4) {
534                         if (StringUtils.isNotEmpty(parameter.getExpression())) {
535                             GeneratorUtils.element(w, "expression", parameter.getExpression());
536                         }
537                         if (StringUtils.isNotEmpty(parameter.getDefaultValue())) {
538                             GeneratorUtils.element(w, "defaultValue", parameter.getDefaultValue());
539                         }
540                     } else {
541                         if (StringUtils.isNotEmpty(parameter.getDefaultValue())
542                                 || StringUtils.isNotEmpty(parameter.getExpression())) {
543                             configuration.add(parameter);
544                         }
545                     }
546 
547                     w.endElement();
548                 }
549             }
550         }
551 
552         w.endElement();
553 
554         // ----------------------------------------------------------------------
555         // Configuration
556         // ----------------------------------------------------------------------
557 
558         if (!configuration.isEmpty()) {
559             w.startElement("configuration");
560 
561             for (Parameter parameter : configuration) {
562                 if (type == DescriptorType.LIMITED_FOR_HELP_MOJO && !parameter.isEditable()) {
563                     // don't show readonly parameters in help
564                     continue;
565                 }
566 
567                 w.startElement(parameter.getName());
568 
569                 // strip type by parameter type (generics) information
570                 String parameterType = StringUtils.chomp(parameter.getType(), "<");
571                 if (parameterType != null && !parameterType.isEmpty()) {
572                     w.addAttribute("implementation", parameterType);
573                 }
574 
575                 if (parameter.getDefaultValue() != null) {
576                     w.addAttribute("default-value", parameter.getDefaultValue());
577                 }
578 
579                 if (StringUtils.isNotEmpty(parameter.getExpression())) {
580                     w.writeText(parameter.getExpression());
581                 }
582 
583                 w.endElement();
584             }
585 
586             w.endElement();
587         }
588 
589         // ----------------------------------------------------------------------
590         // Requirements
591         // ----------------------------------------------------------------------
592 
593         if (!requirements.isEmpty() && type != DescriptorType.LIMITED_FOR_HELP_MOJO) {
594             w.startElement("requirements");
595 
596             for (Map.Entry<String, Requirement> entry : requirements.entrySet()) {
597                 String key = entry.getKey();
598                 Requirement requirement = entry.getValue();
599 
600                 w.startElement("requirement");
601 
602                 GeneratorUtils.element(w, "role", requirement.getRole());
603 
604                 if (StringUtils.isNotEmpty(requirement.getRoleHint())) {
605                     GeneratorUtils.element(w, "role-hint", requirement.getRoleHint());
606                 }
607 
608                 GeneratorUtils.element(w, "field-name", key);
609 
610                 w.endElement();
611             }
612 
613             w.endElement();
614         }
615 
616         w.endElement();
617     }
618 
619     /**
620      * Writes parameter type information and potentially also the related javadoc URL.
621      * @param w
622      * @param type
623      * @param javadocLinkGenerator
624      * @param parameter
625      * @param goal
626      */
627     protected void writeParameterType(
628             XMLWriter w,
629             DescriptorType type,
630             JavadocLinkGenerator javadocLinkGenerator,
631             Parameter parameter,
632             String goal) {
633         String parameterType = parameter.getType();
634 
635         if (type == DescriptorType.STANDARD) {
636             // strip type by parameter type (generics) information for standard plugin descriptor
637             parameterType = StringUtils.chomp(parameterType, "<");
638         }
639         GeneratorUtils.element(w, "type", parameterType);
640 
641         if (type == DescriptorType.XHTML && javadocLinkGenerator != null) {
642             // skip primitives which never has javadoc
643             if (parameter.getType().indexOf('.') == -1) {
644                 LOG.debug("Javadoc URLs are not available for primitive types like {}", parameter.getType());
645             } else {
646                 try {
647                     URI javadocUrl = getJavadocUrlForType(javadocLinkGenerator, parameterType);
648                     GeneratorUtils.element(w, "typeJavadocUrl", javadocUrl.toString());
649                 } catch (IllegalArgumentException e) {
650                     LOG.warn(
651                             "Could not get javadoc URL for type {} of parameter {} from goal {}: {}",
652                             parameter.getType(),
653                             parameter.getName(),
654                             goal,
655                             e.getMessage());
656                 }
657             }
658         }
659     }
660 
661     private static String extractBinaryNameForJavadoc(String type) {
662         final String binaryName;
663         int startOfParameterType = type.indexOf("<");
664         if (startOfParameterType != -1) {
665             // parse parameter type
666             String mainType = type.substring(0, startOfParameterType);
667 
668             // some heuristics here
669             String[] parameterTypes = type.substring(startOfParameterType + 1, type.lastIndexOf(">"))
670                     .split(",\\s*");
671             switch (parameterTypes.length) {
672                 case 1: // if only one parameter type, assume collection, first parameter type is most interesting
673                     binaryName = extractBinaryNameForJavadoc(parameterTypes[0]);
674                     break;
675                 case 2: // if two parameter types assume map, second parameter type is most interesting
676                     binaryName = extractBinaryNameForJavadoc(parameterTypes[1]);
677                     break;
678                 default:
679                     // all other cases link to main type
680                     binaryName = mainType;
681             }
682         } else {
683             binaryName = type;
684         }
685         return binaryName;
686     }
687 
688     static URI getJavadocUrlForType(JavadocLinkGenerator javadocLinkGenerator, String type) {
689         return javadocLinkGenerator.createLink(extractBinaryNameForJavadoc(type));
690     }
691 
692     /**
693      * Get the expression value, eventually surrounding it with <code>${ }</code>.
694      *
695      * @param parameter the parameter
696      * @return the expression value
697      */
698     private String getExpression(Parameter parameter) {
699         String expression = parameter.getExpression();
700         if (StringUtils.isNotBlank(expression) && !expression.contains("${")) {
701             expression = "${" + expression.trim() + "}";
702             parameter.setExpression(expression);
703         }
704         return expression;
705     }
706 }