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