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