1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.tools.plugin.extractor.javadoc;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23
24 import java.io.File;
25 import java.net.MalformedURLException;
26 import java.net.URL;
27 import java.net.URLClassLoader;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.TreeMap;
33
34 import com.thoughtworks.qdox.JavaProjectBuilder;
35 import com.thoughtworks.qdox.library.SortedClassLibraryBuilder;
36 import com.thoughtworks.qdox.model.DocletTag;
37 import com.thoughtworks.qdox.model.JavaClass;
38 import com.thoughtworks.qdox.model.JavaField;
39 import com.thoughtworks.qdox.model.JavaType;
40 import org.apache.maven.artifact.Artifact;
41 import org.apache.maven.plugin.descriptor.InvalidParameterException;
42 import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
43 import org.apache.maven.plugin.descriptor.MojoDescriptor;
44 import org.apache.maven.plugin.descriptor.Parameter;
45 import org.apache.maven.plugin.descriptor.Requirement;
46 import org.apache.maven.project.MavenProject;
47 import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
48 import org.apache.maven.tools.plugin.PluginToolsRequest;
49 import org.apache.maven.tools.plugin.extractor.ExtractionException;
50 import org.apache.maven.tools.plugin.extractor.GroupKey;
51 import org.apache.maven.tools.plugin.extractor.MojoDescriptorExtractor;
52 import org.apache.maven.tools.plugin.util.PluginUtils;
53 import org.codehaus.plexus.logging.AbstractLogEnabled;
54 import org.codehaus.plexus.util.StringUtils;
55
56
57
58
59
60
61
62
63
64
65
66
67
68 @Named(JavaJavadocMojoDescriptorExtractor.NAME)
69 @Singleton
70 public class JavaJavadocMojoDescriptorExtractor extends AbstractLogEnabled
71 implements MojoDescriptorExtractor, JavadocMojoAnnotation {
72 public static final String NAME = "java-javadoc";
73
74 private static final GroupKey GROUP_KEY = new GroupKey(GroupKey.JAVA_GROUP, 200);
75
76 @Override
77 public String getName() {
78 return NAME;
79 }
80
81 @Override
82 public boolean isDeprecated() {
83 return true;
84 }
85
86 @Override
87 public GroupKey getGroupKey() {
88 return GROUP_KEY;
89 }
90
91
92
93
94
95
96 protected void validateParameter(Parameter parameter, int i) throws InvalidParameterException {
97
98 String name = parameter.getName();
99
100 if (name == null) {
101 throw new InvalidParameterException("name", i);
102 }
103
104
105 String type = parameter.getType();
106
107 if (type == null) {
108 throw new InvalidParameterException("type", i);
109 }
110
111
112 String description = parameter.getDescription();
113
114 if (description == null) {
115 throw new InvalidParameterException("description", i);
116 }
117 }
118
119
120
121
122
123
124
125
126
127
128 protected MojoDescriptor createMojoDescriptor(JavaClass javaClass) throws InvalidPluginDescriptorException {
129 ExtendedMojoDescriptor mojoDescriptor = new ExtendedMojoDescriptor();
130 mojoDescriptor.setLanguage("java");
131 mojoDescriptor.setImplementation(javaClass.getFullyQualifiedName());
132 mojoDescriptor.setDescription(javaClass.getComment());
133
134
135
136
137
138
139 DocletTag aggregator = findInClassHierarchy(javaClass, JavadocMojoAnnotation.AGGREGATOR);
140 if (aggregator != null) {
141 mojoDescriptor.setAggregator(true);
142 }
143
144
145 DocletTag configurator = findInClassHierarchy(javaClass, JavadocMojoAnnotation.CONFIGURATOR);
146 if (configurator != null) {
147 mojoDescriptor.setComponentConfigurator(configurator.getValue());
148 }
149
150
151 DocletTag execute = findInClassHierarchy(javaClass, JavadocMojoAnnotation.EXECUTE);
152 if (execute != null) {
153 String executePhase = execute.getNamedParameter(JavadocMojoAnnotation.EXECUTE_PHASE);
154 String executeGoal = execute.getNamedParameter(JavadocMojoAnnotation.EXECUTE_GOAL);
155
156 if (executePhase == null && executeGoal == null) {
157 throw new InvalidPluginDescriptorException(javaClass.getFullyQualifiedName()
158 + ": @execute tag requires either a 'phase' or 'goal' parameter");
159 } else if (executePhase != null && executeGoal != null) {
160 throw new InvalidPluginDescriptorException(javaClass.getFullyQualifiedName()
161 + ": @execute tag can have only one of a 'phase' or 'goal' parameter");
162 }
163 mojoDescriptor.setExecutePhase(executePhase);
164 mojoDescriptor.setExecuteGoal(executeGoal);
165
166 String lifecycle = execute.getNamedParameter(JavadocMojoAnnotation.EXECUTE_LIFECYCLE);
167 if (lifecycle != null) {
168 mojoDescriptor.setExecuteLifecycle(lifecycle);
169 if (mojoDescriptor.getExecuteGoal() != null) {
170 throw new InvalidPluginDescriptorException(javaClass.getFullyQualifiedName()
171 + ": @execute lifecycle requires a phase instead of a goal");
172 }
173 }
174 }
175
176
177 DocletTag goal = findInClassHierarchy(javaClass, JavadocMojoAnnotation.GOAL);
178 if (goal != null) {
179 mojoDescriptor.setGoal(goal.getValue());
180 }
181
182
183 boolean value = getBooleanTagValue(
184 javaClass, JavadocMojoAnnotation.INHERIT_BY_DEFAULT, mojoDescriptor.isInheritedByDefault());
185 mojoDescriptor.setInheritedByDefault(value);
186
187
188 DocletTag tag = findInClassHierarchy(javaClass, JavadocMojoAnnotation.INSTANTIATION_STRATEGY);
189 if (tag != null) {
190 mojoDescriptor.setInstantiationStrategy(tag.getValue());
191 }
192
193
194 tag = findInClassHierarchy(javaClass, JavadocMojoAnnotation.MULTI_EXECUTION_STRATEGY);
195 if (tag != null) {
196 getLogger()
197 .warn("@" + JavadocMojoAnnotation.MULTI_EXECUTION_STRATEGY + " in "
198 + javaClass.getFullyQualifiedName() + " is deprecated: please use '@"
199 + JavadocMojoAnnotation.EXECUTION_STATEGY + " always' instead.");
200 mojoDescriptor.setExecutionStrategy(MojoDescriptor.MULTI_PASS_EXEC_STRATEGY);
201 } else {
202 mojoDescriptor.setExecutionStrategy(MojoDescriptor.SINGLE_PASS_EXEC_STRATEGY);
203 }
204 tag = findInClassHierarchy(javaClass, JavadocMojoAnnotation.EXECUTION_STATEGY);
205 if (tag != null) {
206 mojoDescriptor.setExecutionStrategy(tag.getValue());
207 }
208
209
210 DocletTag phase = findInClassHierarchy(javaClass, JavadocMojoAnnotation.PHASE);
211 if (phase != null) {
212 mojoDescriptor.setPhase(phase.getValue());
213 }
214
215
216 DocletTag requiresDependencyResolution =
217 findInClassHierarchy(javaClass, JavadocMojoAnnotation.REQUIRES_DEPENDENCY_RESOLUTION);
218 if (requiresDependencyResolution != null) {
219 String v = requiresDependencyResolution.getValue();
220
221 if (StringUtils.isEmpty(v)) {
222 v = "runtime";
223 }
224
225 mojoDescriptor.setDependencyResolutionRequired(v);
226 }
227
228
229 DocletTag requiresDependencyCollection =
230 findInClassHierarchy(javaClass, JavadocMojoAnnotation.REQUIRES_DEPENDENCY_COLLECTION);
231 if (requiresDependencyCollection != null) {
232 String v = requiresDependencyCollection.getValue();
233
234 if (StringUtils.isEmpty(v)) {
235 v = "runtime";
236 }
237
238 mojoDescriptor.setDependencyCollectionRequired(v);
239 }
240
241
242 value = getBooleanTagValue(
243 javaClass, JavadocMojoAnnotation.REQUIRES_DIRECT_INVOCATION, mojoDescriptor.isDirectInvocationOnly());
244 mojoDescriptor.setDirectInvocationOnly(value);
245
246
247 value = getBooleanTagValue(javaClass, JavadocMojoAnnotation.REQUIRES_ONLINE, mojoDescriptor.isOnlineRequired());
248 mojoDescriptor.setOnlineRequired(value);
249
250
251 value = getBooleanTagValue(
252 javaClass, JavadocMojoAnnotation.REQUIRES_PROJECT, mojoDescriptor.isProjectRequired());
253 mojoDescriptor.setProjectRequired(value);
254
255
256 value = getBooleanTagValue(
257 javaClass, JavadocMojoAnnotation.REQUIRES_REPORTS, mojoDescriptor.isRequiresReports());
258 mojoDescriptor.setRequiresReports(value);
259
260
261
262
263
264
265 DocletTag deprecated = javaClass.getTagByName(JavadocMojoAnnotation.DEPRECATED);
266 if (deprecated != null) {
267 mojoDescriptor.setDeprecated(deprecated.getValue());
268 }
269
270
271 DocletTag since = findInClassHierarchy(javaClass, JavadocMojoAnnotation.SINCE);
272 if (since != null) {
273 mojoDescriptor.setSince(since.getValue());
274 }
275
276
277
278 value = getBooleanTagValue(javaClass, JavadocMojoAnnotation.THREAD_SAFE, true, mojoDescriptor.isThreadSafe());
279 mojoDescriptor.setThreadSafe(value);
280
281 extractParameters(mojoDescriptor, javaClass);
282
283 return mojoDescriptor;
284 }
285
286
287
288
289
290
291
292
293 private static boolean getBooleanTagValue(JavaClass javaClass, String tagName, boolean defaultValue) {
294 DocletTag tag = findInClassHierarchy(javaClass, tagName);
295
296 if (tag != null) {
297 String value = tag.getValue();
298
299 if (StringUtils.isNotEmpty(value)) {
300 defaultValue = Boolean.valueOf(value).booleanValue();
301 }
302 }
303 return defaultValue;
304 }
305
306
307
308
309
310
311
312
313
314 private static boolean getBooleanTagValue(
315 JavaClass javaClass, String tagName, boolean defaultForTag, boolean defaultValue) {
316 DocletTag tag = findInClassHierarchy(javaClass, tagName);
317
318 if (tag != null) {
319 String value = tag.getValue();
320
321 if (StringUtils.isNotEmpty(value)) {
322 return Boolean.valueOf(value).booleanValue();
323 } else {
324 return defaultForTag;
325 }
326 }
327 return defaultValue;
328 }
329
330
331
332
333
334
335 private static DocletTag findInClassHierarchy(JavaClass javaClass, String tagName) {
336 DocletTag tag = javaClass.getTagByName(tagName);
337
338 if (tag == null) {
339 JavaClass superClass = javaClass.getSuperJavaClass();
340
341 if (superClass != null) {
342 tag = findInClassHierarchy(superClass, tagName);
343 }
344 }
345
346 return tag;
347 }
348
349
350
351
352
353
354 private void extractParameters(MojoDescriptor mojoDescriptor, JavaClass javaClass)
355 throws InvalidPluginDescriptorException {
356
357
358
359
360 Map<String, JavaField> rawParams = extractFieldParameterTags(javaClass);
361
362 for (Map.Entry<String, JavaField> entry : rawParams.entrySet()) {
363 JavaField field = entry.getValue();
364
365 JavaType type = field.getType();
366
367 Parameter pd = new Parameter();
368
369 pd.setName(entry.getKey());
370
371 pd.setType(type.getFullyQualifiedName());
372
373 pd.setDescription(field.getComment());
374
375 DocletTag deprecationTag = field.getTagByName(JavadocMojoAnnotation.DEPRECATED);
376
377 if (deprecationTag != null) {
378 pd.setDeprecated(deprecationTag.getValue());
379 }
380
381 DocletTag sinceTag = field.getTagByName(JavadocMojoAnnotation.SINCE);
382 if (sinceTag != null) {
383 pd.setSince(sinceTag.getValue());
384 }
385
386 DocletTag componentTag = field.getTagByName(JavadocMojoAnnotation.COMPONENT);
387
388 if (componentTag != null) {
389
390 String role = componentTag.getNamedParameter(JavadocMojoAnnotation.COMPONENT_ROLE);
391
392 if (role == null) {
393 role = field.getType().toString();
394 }
395
396 String roleHint = componentTag.getNamedParameter(JavadocMojoAnnotation.COMPONENT_ROLEHINT);
397
398 if (roleHint == null) {
399
400 roleHint = componentTag.getNamedParameter("role-hint");
401 }
402
403
404
405
406 boolean isDeprecated = PluginUtils.MAVEN_COMPONENTS.containsValue(role);
407
408 if (!isDeprecated) {
409
410 pd.setRequirement(new Requirement(role, roleHint));
411 } else {
412
413 getLogger()
414 .warn("Deprecated @component Javadoc tag for '" + pd.getName() + "' field in "
415 + javaClass.getFullyQualifiedName()
416 + ": replace with @Parameter( defaultValue = \"" + role
417 + "\", readonly = true )");
418 pd.setDefaultValue(role);
419 pd.setRequired(true);
420 }
421
422 pd.setEditable(false);
423
424
425
426 } else {
427
428 DocletTag parameter = field.getTagByName(JavadocMojoAnnotation.PARAMETER);
429
430 pd.setRequired(field.getTagByName(JavadocMojoAnnotation.REQUIRED) != null);
431
432 pd.setEditable(field.getTagByName(JavadocMojoAnnotation.READONLY) == null);
433
434 String name = parameter.getNamedParameter(JavadocMojoAnnotation.PARAMETER_NAME);
435
436 if (!StringUtils.isEmpty(name)) {
437 pd.setName(name);
438 }
439
440 String alias = parameter.getNamedParameter(JavadocMojoAnnotation.PARAMETER_ALIAS);
441
442 if (!StringUtils.isEmpty(alias)) {
443 pd.setAlias(alias);
444 }
445
446 String expression = parameter.getNamedParameter(JavadocMojoAnnotation.PARAMETER_EXPRESSION);
447 String property = parameter.getNamedParameter(JavadocMojoAnnotation.PARAMETER_PROPERTY);
448
449 if (StringUtils.isNotEmpty(expression) && StringUtils.isNotEmpty(property)) {
450 getLogger().error(javaClass.getFullyQualifiedName() + "#" + field.getName() + ":");
451 getLogger().error(" Cannot use both:");
452 getLogger().error(" @parameter expression=\"${property}\"");
453 getLogger().error(" and");
454 getLogger().error(" @parameter property=\"property\"");
455 getLogger().error(" Second syntax is preferred.");
456 throw new InvalidParameterException(
457 javaClass.getFullyQualifiedName() + "#" + field.getName() + ": cannot"
458 + " use both @parameter expression and property",
459 null);
460 }
461
462 if (StringUtils.isNotEmpty(expression)) {
463 getLogger().warn(javaClass.getFullyQualifiedName() + "#" + field.getName() + ":");
464 getLogger().warn(" The syntax");
465 getLogger().warn(" @parameter expression=\"${property}\"");
466 getLogger().warn(" is deprecated, please use");
467 getLogger().warn(" @parameter property=\"property\"");
468 getLogger().warn(" instead.");
469
470 } else if (StringUtils.isNotEmpty(property)) {
471 expression = "${" + property + "}";
472 }
473
474 pd.setExpression(expression);
475
476 if (StringUtils.isNotEmpty(expression) && expression.startsWith("${component.")) {
477 getLogger().warn(javaClass.getFullyQualifiedName() + "#" + field.getName() + ":");
478 getLogger().warn(" The syntax");
479 getLogger().warn(" @parameter expression=\"${component.<role>#<roleHint>}\"");
480 getLogger().warn(" is deprecated, please use");
481 getLogger().warn(" @component role=\"<role>\" roleHint=\"<roleHint>\"");
482 getLogger().warn(" instead.");
483 }
484
485 if ("${reports}".equals(pd.getExpression())) {
486 mojoDescriptor.setRequiresReports(true);
487 }
488
489 pd.setDefaultValue(parameter.getNamedParameter(JavadocMojoAnnotation.PARAMETER_DEFAULT_VALUE));
490
491 pd.setImplementation(parameter.getNamedParameter(JavadocMojoAnnotation.PARAMETER_IMPLEMENTATION));
492 }
493
494 mojoDescriptor.addParameter(pd);
495 }
496 }
497
498
499
500
501
502
503
504 private Map<String, JavaField> extractFieldParameterTags(JavaClass javaClass) {
505 Map<String, JavaField> rawParams;
506
507
508
509 JavaClass superClass = javaClass.getSuperJavaClass();
510
511 if (superClass != null) {
512 rawParams = extractFieldParameterTags(superClass);
513 } else {
514 rawParams = new TreeMap<String, JavaField>();
515 }
516
517 for (JavaField field : javaClass.getFields()) {
518 if (field.getTagByName(JavadocMojoAnnotation.PARAMETER) != null
519 || field.getTagByName(JavadocMojoAnnotation.COMPONENT) != null) {
520 rawParams.put(field.getName(), field);
521 }
522 }
523 return rawParams;
524 }
525
526 @Override
527 public List<MojoDescriptor> execute(PluginToolsRequest request)
528 throws ExtractionException, InvalidPluginDescriptorException {
529 Collection<JavaClass> javaClasses = discoverClasses(request);
530
531 List<MojoDescriptor> descriptors = new ArrayList<>();
532
533 for (JavaClass javaClass : javaClasses) {
534 DocletTag tag = javaClass.getTagByName(GOAL);
535
536 if (tag != null) {
537 MojoDescriptor mojoDescriptor = createMojoDescriptor(javaClass);
538 mojoDescriptor.setPluginDescriptor(request.getPluginDescriptor());
539
540
541 validate(mojoDescriptor);
542
543 descriptors.add(mojoDescriptor);
544 }
545 }
546
547 return descriptors;
548 }
549
550
551
552
553
554 protected Collection<JavaClass> discoverClasses(final PluginToolsRequest request) {
555 JavaProjectBuilder builder = new JavaProjectBuilder(new SortedClassLibraryBuilder());
556 builder.setEncoding(request.getEncoding());
557
558
559 List<URL> urls = new ArrayList<>(request.getDependencies().size());
560 for (Artifact artifact : request.getDependencies()) {
561 try {
562 urls.add(artifact.getFile().toURI().toURL());
563 } catch (MalformedURLException e) {
564
565 }
566 }
567 builder.addClassLoader(new URLClassLoader(urls.toArray(new URL[0]), ClassLoader.getSystemClassLoader()));
568
569 MavenProject project = request.getProject();
570
571 for (String source : project.getCompileSourceRoots()) {
572 builder.addSourceTree(new File(source));
573 }
574
575
576 File generatedPlugin = new File(project.getBasedir(), "target/generated-sources/plugin");
577 if (!project.getCompileSourceRoots().contains(generatedPlugin.getAbsolutePath())) {
578 builder.addSourceTree(generatedPlugin);
579 }
580
581 return builder.getClasses();
582 }
583
584
585
586
587
588 protected void validate(MojoDescriptor mojoDescriptor) throws InvalidParameterException {
589 List<Parameter> parameters = mojoDescriptor.getParameters();
590
591 if (parameters != null) {
592 for (int j = 0; j < parameters.size(); j++) {
593 validateParameter(parameters.get(j), j);
594 }
595 }
596 }
597 }