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