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