View Javadoc

1   package org.apache.maven.tools.plugin.extractor.java;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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.JavaSource;
27  import com.thoughtworks.qdox.model.Type;
28  
29  import org.apache.maven.plugin.descriptor.InvalidParameterException;
30  import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
31  import org.apache.maven.plugin.descriptor.MojoDescriptor;
32  import org.apache.maven.plugin.descriptor.Parameter;
33  import org.apache.maven.plugin.descriptor.PluginDescriptor;
34  import org.apache.maven.plugin.descriptor.Requirement;
35  import org.apache.maven.project.MavenProject;
36  import org.apache.maven.tools.plugin.extractor.MojoDescriptorExtractor;
37  import org.codehaus.plexus.logging.AbstractLogEnabled;
38  import org.codehaus.plexus.util.StringUtils;
39  
40  import java.io.File;
41  import java.util.ArrayList;
42  import java.util.Iterator;
43  import java.util.List;
44  import java.util.Map;
45  import java.util.TreeMap;
46  
47  
48  /**
49   * @todo add example usage tag that can be shown in the doco
50   * @todo need to add validation directives so that systems embedding maven2 can
51   * get validation directives to help users in IDEs.
52   */
53  public class JavaMojoDescriptorExtractor
54      extends AbstractLogEnabled
55      implements MojoDescriptorExtractor
56  {
57      public static final String MAVEN_PLUGIN_INSTANTIATION = "instantiationStrategy";
58  
59      public static final String CONFIGURATOR = "configurator";
60  
61      public static final String PARAMETER = "parameter";
62  
63      public static final String PARAMETER_EXPRESSION = "expression";
64  
65      public static final String PARAMETER_DEFAULT_VALUE = "default-value";
66  
67      public static final String PARAMETER_ALIAS = "alias";
68  
69      public static final String SINCE = "since";
70  
71      /**
72       * This indicates the base name of the bean properties used to read/write this parameter's value.
73       * So:
74       *
75       * @parameter property="project"
76       * <p/>
77       * Would say there is a getProject() method and a setProject(Project) method. Here the field
78       * name would not be the basis for the parameter's name. This mode of operation will allow the
79       * mojos to be usable as beans and will be the promoted form of use.
80       */
81      public static final String PARAMETER_PROPERTY = "property";
82  
83      public static final String REQUIRED = "required";
84  
85      public static final String DEPRECATED = "deprecated";
86  
87      public static final String READONLY = "readonly";
88  
89      public static final String GOAL = "goal";
90  
91      public static final String PHASE = "phase";
92  
93      public static final String EXECUTE = "execute";
94  
95      public static final String EXECUTE_LIFECYCLE = "lifecycle";
96  
97      public static final String EXECUTE_PHASE = "phase";
98  
99      public static final String EXECUTE_GOAL = "goal";
100 
101     public static final String GOAL_DESCRIPTION = "description";
102 
103     public static final String GOAL_REQUIRES_DEPENDENCY_RESOLUTION = "requiresDependencyResolution";
104 
105     public static final String GOAL_REQUIRES_PROJECT = "requiresProject";
106 
107     public static final String GOAL_REQUIRES_REPORTS = "requiresReports";
108 
109     public static final String GOAL_IS_AGGREGATOR = "aggregator";
110 
111     public static final String GOAL_REQUIRES_ONLINE = "requiresOnline";
112 
113     public static final String GOAL_INHERIT_BY_DEFAULT = "inheritByDefault";
114 
115     public static final String GOAL_MULTI_EXECUTION_STRATEGY = "attainAlways";
116 
117     public static final String GOAL_REQUIRES_DIRECT_INVOCATION = "requiresDirectInvocation";
118 
119     private static final String COMPONENT = "component";
120 
121     private static final String COMPONENT_ROLE = "role";
122 
123     private static final String COMPONENT_ROLEHINT = "roleHint";
124 
125     protected void validateParameter( Parameter parameter, int i )
126         throws InvalidParameterException
127     {
128         // TODO: remove when backward compatibility is no longer an issue.
129         String name = parameter.getName();
130 
131         if ( name == null )
132         {
133             throw new InvalidParameterException( "name", i );
134         }
135 
136         // TODO: remove when backward compatibility is no longer an issue.
137         String type = parameter.getType();
138 
139         if ( type == null )
140         {
141             throw new InvalidParameterException( "type", i );
142         }
143 
144         // TODO: remove when backward compatibility is no longer an issue.
145         String description = parameter.getDescription();
146 
147         if ( description == null )
148         {
149             throw new InvalidParameterException( "description", i );
150         }
151     }
152 
153     // ----------------------------------------------------------------------
154     // Mojo descriptor creation from @tags
155     // ----------------------------------------------------------------------
156 
157     private MojoDescriptor createMojoDescriptor( JavaSource javaSource, PluginDescriptor pluginDescriptor )
158         throws InvalidPluginDescriptorException
159     {
160         MojoDescriptor mojoDescriptor = new MojoDescriptor();
161 
162         mojoDescriptor.setPluginDescriptor( pluginDescriptor );
163 
164         JavaClass javaClass = getJavaClass( javaSource );
165 
166         mojoDescriptor.setLanguage( "java" );
167 
168         mojoDescriptor.setImplementation( javaClass.getFullyQualifiedName() );
169 
170         mojoDescriptor.setDescription( javaClass.getComment() );
171 
172         DocletTag tag = findInClassHierarchy( javaClass, MAVEN_PLUGIN_INSTANTIATION );
173 
174         if ( tag != null )
175         {
176             mojoDescriptor.setInstantiationStrategy( tag.getValue() );
177         }
178 
179         tag = findInClassHierarchy( javaClass, GOAL_MULTI_EXECUTION_STRATEGY );
180 
181         if ( tag != null )
182         {
183             mojoDescriptor.setExecutionStrategy( MojoDescriptor.MULTI_PASS_EXEC_STRATEGY );
184         }
185         else
186         {
187             mojoDescriptor.setExecutionStrategy( MojoDescriptor.SINGLE_PASS_EXEC_STRATEGY );
188         }
189 
190         // ----------------------------------------------------------------------
191         // Configurator hint
192         // ----------------------------------------------------------------------
193 
194         DocletTag configurator = findInClassHierarchy( javaClass, CONFIGURATOR );
195 
196         if ( configurator != null )
197         {
198             mojoDescriptor.setComponentConfigurator( configurator.getValue() );
199         }
200 
201         // ----------------------------------------------------------------------
202         // Goal name
203         // ----------------------------------------------------------------------
204 
205         DocletTag goal = findInClassHierarchy( javaClass, GOAL );
206 
207         if ( goal != null )
208         {
209             mojoDescriptor.setGoal( goal.getValue() );
210         }
211 
212         // ----------------------------------------------------------------------
213         // Phase name
214         // ----------------------------------------------------------------------
215 
216         DocletTag phase = findInClassHierarchy( javaClass, PHASE );
217 
218         if ( phase != null )
219         {
220             mojoDescriptor.setPhase( phase.getValue() );
221         }
222 
223         // ----------------------------------------------------------------------
224         // Additional phase to execute first
225         // ----------------------------------------------------------------------
226 
227         DocletTag execute = findInClassHierarchy( javaClass, EXECUTE );
228 
229         if ( execute != null )
230         {
231             String executePhase = execute.getNamedParameter( EXECUTE_PHASE );
232             String executeGoal = execute.getNamedParameter( EXECUTE_GOAL );
233 
234             if ( executePhase == null && executeGoal == null )
235             {
236                 throw new InvalidPluginDescriptorException( "@execute tag requires a 'phase' or 'goal' parameter" );
237             }
238             else if ( executePhase != null && executeGoal != null )
239             {
240                 throw new InvalidPluginDescriptorException(
241                     "@execute tag can have only one of a 'phase' or 'goal' parameter" );
242             }
243             mojoDescriptor.setExecutePhase( executePhase );
244             mojoDescriptor.setExecuteGoal( executeGoal );
245 
246             String lifecycle = execute.getNamedParameter( EXECUTE_LIFECYCLE );
247 
248             if ( lifecycle != null )
249             {
250                 mojoDescriptor.setExecuteLifecycle( lifecycle );
251                 if ( mojoDescriptor.getExecuteGoal() != null )
252                 {
253                     throw new InvalidPluginDescriptorException(
254                         "@execute lifecycle requires a phase instead of a goal" );
255                 }
256             }
257         }
258 
259         // ----------------------------------------------------------------------
260         // Dependency resolution flag
261         // ----------------------------------------------------------------------
262 
263         DocletTag requiresDependencyResolution = findInClassHierarchy( javaClass, GOAL_REQUIRES_DEPENDENCY_RESOLUTION );
264 
265         if ( requiresDependencyResolution != null )
266         {
267             String value = requiresDependencyResolution.getValue();
268 
269             if ( value == null || value.length() == 0 )
270             {
271                 value = "runtime";
272             }
273 
274             mojoDescriptor.setDependencyResolutionRequired( value );
275         }
276 
277         // ----------------------------------------------------------------------
278         // Project flag
279         // ----------------------------------------------------------------------
280 
281         boolean value = getBooleanTagValue( javaClass, GOAL_REQUIRES_PROJECT, mojoDescriptor.isProjectRequired() );
282         mojoDescriptor.setProjectRequired( value );
283 
284         // ----------------------------------------------------------------------
285         // Aggregator flag
286         // ----------------------------------------------------------------------
287 
288         DocletTag aggregator = findInClassHierarchy( javaClass, GOAL_IS_AGGREGATOR );
289 
290         if ( aggregator != null )
291         {
292             mojoDescriptor.setAggregator( true );
293         }
294 
295         // ----------------------------------------------------------------------
296         // requiresDirectInvocation flag
297         // ----------------------------------------------------------------------
298 
299         value =
300             getBooleanTagValue( javaClass, GOAL_REQUIRES_DIRECT_INVOCATION, mojoDescriptor.isDirectInvocationOnly() );
301         mojoDescriptor.setDirectInvocationOnly( value );
302 
303         // ----------------------------------------------------------------------
304         // Online flag
305         // ----------------------------------------------------------------------
306 
307         value = getBooleanTagValue( javaClass, GOAL_REQUIRES_ONLINE, mojoDescriptor.isOnlineRequired() );
308         mojoDescriptor.setOnlineRequired( value );
309 
310         // ----------------------------------------------------------------------
311         // inheritByDefault flag
312         // ----------------------------------------------------------------------
313 
314         value = getBooleanTagValue( javaClass, GOAL_INHERIT_BY_DEFAULT, mojoDescriptor.isInheritedByDefault() );
315         mojoDescriptor.setInheritedByDefault( value );
316 
317         extractParameters( mojoDescriptor, javaClass );
318 
319         return mojoDescriptor;
320     }
321 
322     private static boolean getBooleanTagValue( JavaClass javaClass, String tagName, boolean defaultValue )
323     {
324         DocletTag requiresProject = findInClassHierarchy( javaClass, tagName );
325 
326         if ( requiresProject != null )
327         {
328             String requiresProjectValue = requiresProject.getValue();
329 
330             if ( requiresProjectValue != null && requiresProjectValue.length() > 0 )
331             {
332                 defaultValue = Boolean.valueOf( requiresProjectValue ).booleanValue();
333             }
334         }
335         return defaultValue;
336     }
337 
338     private static DocletTag findInClassHierarchy( JavaClass javaClass, String tagName )
339     {
340         DocletTag tag = javaClass.getTagByName( tagName );
341 
342         if ( tag == null )
343         {
344             JavaClass superClass = javaClass.getSuperJavaClass();
345 
346             if ( superClass != null )
347             {
348                 tag = findInClassHierarchy( superClass, tagName );
349             }
350         }
351 
352         return tag;
353     }
354 
355     private void extractParameters( MojoDescriptor mojoDescriptor, JavaClass javaClass )
356         throws InvalidPluginDescriptorException
357     {
358         // ---------------------------------------------------------------------------------
359         // We're resolving class-level, ancestor-class-field, local-class-field order here.
360         // ---------------------------------------------------------------------------------
361 
362         Map rawParams = extractFieldParameterTags( javaClass );
363 
364         for ( Iterator it = rawParams.entrySet().iterator(); it.hasNext(); )
365         {
366             Map.Entry entry = (Map.Entry) it.next();
367 
368             JavaField field = (JavaField) entry.getValue();
369 
370             Type type = field.getType();
371 
372             Parameter pd = new Parameter();
373 
374             if ( !type.isArray() )
375             {
376                 pd.setType( type.getValue() );
377             }
378             else
379             {
380                 StringBuffer value = new StringBuffer( type.getValue() );
381 
382                 int remaining = type.getDimensions();
383 
384                 while ( remaining-- > 0 )
385                 {
386                     value.append( "[]" );
387                 }
388 
389                 pd.setType( value.toString() );
390             }
391 
392             pd.setDescription( field.getComment() );
393 
394             DocletTag componentTag = field.getTagByName( COMPONENT );
395 
396             if ( componentTag != null )
397             {
398                 String role = componentTag.getNamedParameter( COMPONENT_ROLE );
399 
400                 if ( role == null )
401                 {
402                     role = field.getType().toString();
403                 }
404 
405                 String roleHint = componentTag.getNamedParameter( COMPONENT_ROLEHINT );
406 
407                 if ( roleHint == null )
408                 {
409                     // support alternate syntax for better compatibility with the Plexus CDC.
410                     roleHint = componentTag.getNamedParameter( "role-hint" );
411                 }
412 
413                 pd.setRequirement( new Requirement( role, roleHint ) );
414 
415                 pd.setName( (String) entry.getKey() );
416             }
417             else
418             {
419                 DocletTag parameter = field.getTagByName( PARAMETER );
420 
421                 // ----------------------------------------------------------------------
422                 // We will look for a property name here first and use that if present
423                 // i.e:
424                 //
425                 // @parameter property="project"
426                 //
427                 // Which will become the name used for the configuration element which
428                 // will in turn will allow plexus to use the corresponding setter.
429                 // ----------------------------------------------------------------------
430 
431                 String property = parameter.getNamedParameter( PARAMETER_PROPERTY );
432 
433                 if ( !StringUtils.isEmpty( property ) )
434                 {
435                     pd.setName( property );
436                 }
437                 else
438                 {
439                     pd.setName( (String) entry.getKey() );
440                 }
441 
442                 pd.setRequired( field.getTagByName( REQUIRED ) != null );
443 
444                 pd.setEditable( field.getTagByName( READONLY ) == null );
445 
446                 DocletTag deprecationTag = field.getTagByName( DEPRECATED );
447 
448                 if ( deprecationTag != null )
449                 {
450                     pd.setDeprecated( deprecationTag.getValue() );
451                 }
452 
453                 String alias = parameter.getNamedParameter( PARAMETER_ALIAS );
454 
455                 if ( !StringUtils.isEmpty( alias ) )
456                 {
457                     pd.setAlias( alias );
458                 }
459 
460                 pd.setExpression( parameter.getNamedParameter( PARAMETER_EXPRESSION ) );
461 
462                 if ( "${reports}".equals( pd.getExpression() ) )
463                 {
464                     mojoDescriptor.setRequiresReports( true );
465                 }
466 
467                 pd.setDefaultValue( parameter.getNamedParameter( PARAMETER_DEFAULT_VALUE ) );
468             }
469 
470             mojoDescriptor.addParameter( pd );
471         }
472     }
473 
474     private Map extractFieldParameterTags( JavaClass javaClass )
475     {
476         Map rawParams;
477 
478         // we have to add the parent fields first, so that they will be overwritten by the local fields if
479         // that actually happens...
480         JavaClass superClass = javaClass.getSuperJavaClass();
481 
482         if ( superClass != null )
483         {
484             rawParams = extractFieldParameterTags( superClass );
485         }
486         else
487         {
488             rawParams = new TreeMap();
489         }
490 
491         JavaField[] classFields = javaClass.getFields();
492 
493         if ( classFields != null )
494         {
495             for ( int i = 0; i < classFields.length; i++ )
496             {
497                 JavaField field = classFields[i];
498 
499                 if ( field.getTagByName( PARAMETER ) != null || field.getTagByName( COMPONENT ) != null )
500                 {
501                     rawParams.put( field.getName(), field );
502                 }
503             }
504         }
505         return rawParams;
506     }
507 
508     private JavaClass getJavaClass( JavaSource javaSource )
509     {
510         return javaSource.getClasses()[0];
511     }
512 
513     public List execute( MavenProject project, PluginDescriptor pluginDescriptor )
514         throws InvalidPluginDescriptorException
515     {
516         JavaDocBuilder builder = new JavaDocBuilder();
517 
518         for ( Iterator i = project.getCompileSourceRoots().iterator(); i.hasNext(); )
519         {
520             builder.addSourceTree( new File( (String) i.next() ) );
521         }
522 
523         JavaSource[] javaSources = builder.getSources();
524 
525         List descriptors = new ArrayList();
526 
527         for ( int i = 0; i < javaSources.length; i++ )
528         {
529             JavaClass javaClass = getJavaClass( javaSources[i] );
530 
531             DocletTag tag = javaClass.getTagByName( GOAL );
532 
533             if ( tag != null )
534             {
535                 MojoDescriptor mojoDescriptor = createMojoDescriptor( javaSources[i], pluginDescriptor );
536 
537                 // ----------------------------------------------------------------------
538                 // Validate the descriptor as best we can before allowing it
539                 // to be processed.
540                 // ----------------------------------------------------------------------
541 
542                 List parameters = mojoDescriptor.getParameters();
543 
544                 if ( parameters != null )
545                 {
546                     for ( int j = 0; j < parameters.size(); j++ )
547                     {
548                         validateParameter( (Parameter) parameters.get( j ), j );
549                     }
550                 }
551 
552                 //                Commented because it causes a VerifyError:
553                 //                java.lang.VerifyError:
554                 //                (class:
555                 // org/apache/maven/tools/plugin/extractor/java/JavaMojoDescriptorExtractor,
556                 //                method: execute signature:
557                 // (Ljava/lang/String;Lorg/apache/maven/project/MavenProject;)Ljava/util/Set;)
558                 //                Incompatible object argument for function call
559                 //
560                 //                Refactored to allow MavenMojoDescriptor.getComponentFactory()
561                 //                return MavenMojoDescriptor.getMojoDescriptor().getLanguage(),
562                 //                and removed all usage of MavenMojoDescriptor from extractors.
563                 //
564                 //
565                 //                MavenMojoDescriptor mmDescriptor = new
566                 // MavenMojoDescriptor(mojoDescriptor);
567                 //
568                 //                JavaClass javaClass = getJavaClass(javaSources[i]);
569                 //
570                 //                mmDescriptor.setImplementation(javaClass.getFullyQualifiedName());
571                 //
572                 //                descriptors.add( mmDescriptor );
573 
574                 descriptors.add( mojoDescriptor );
575             }
576         }
577 
578         return descriptors;
579     }
580 
581 }