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