View Javadoc
1   package org.codehaus.plexus.metadata.gleaner;
2   
3   /*
4    * The MIT License
5    *
6    * Copyright (c) 2004, The Codehaus
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of
9    * this software and associated documentation files (the "Software"), to deal in
10   * the Software without restriction, including without limitation the rights to
11   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12   * of the Software, and to permit persons to whom the Software is furnished to do
13   * so, subject to the following conditions:
14   *
15   * The above copyright notice and this permission notice shall be included in all
16   * copies or substantial portions of the Software.
17   *
18   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24   * SOFTWARE.
25   */
26  
27  import java.util.*;
28  
29  import org.codehaus.plexus.component.repository.ComponentDescriptor;
30  import org.codehaus.plexus.component.repository.ComponentRequirement;
31  import org.codehaus.plexus.component.repository.ComponentRequirementList;
32  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
33  import org.codehaus.plexus.logging.LogEnabled;
34  import org.codehaus.plexus.personality.plexus.lifecycle.phase.*;
35  import org.codehaus.plexus.util.StringUtils;
36  
37  import com.thoughtworks.qdox.JavaProjectBuilder;
38  import com.thoughtworks.qdox.model.DocletTag;
39  import com.thoughtworks.qdox.model.JavaClass;
40  import com.thoughtworks.qdox.model.JavaField;
41  
42  /**
43   * A source component gleaner which uses QDox to discover Javadoc annotations.
44   * 
45   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
46   * @version $Id$
47   */
48  public class QDoxComponentGleaner
49      extends ComponentGleanerSupport
50      implements SourceComponentGleaner
51  {
52      public static final String PLEXUS_COMPONENT_TAG = "plexus.component";
53  
54      public static final String PLEXUS_REQUIREMENT_TAG = "plexus.requirement";
55  
56      public static final String PLEXUS_CONFIGURATION_TAG = "plexus.configuration";
57  
58      public static final String PLEXUS_VERSION_PARAMETER = "version";
59  
60      public static final String PLEXUS_ROLE_PARAMETER = "role";
61  
62      public static final String PLEXUS_ROLE_HINT_PARAMETER = "role-hint";
63  
64      public static final String PLEXUS_ROLE_HINT_LIST_PARAMETER = "role-hints";
65  
66      public static final String PLEXUS_ALIAS_PARAMETER = "alias";
67  
68      public static final String PLEXUS_DEFAULT_VALUE_PARAMETER = "default-value";
69  
70      public static final String PLEXUS_LIFECYCLE_HANDLER_PARAMETER = "lifecycle-handler";
71  
72      public static final String PLEXUS_INSTANTIATION_STARTEGY_PARAMETER = "instantiation-strategy";
73  
74      public static final String PLEXUS_OPTIONAL_PARAMETER = "optional";
75  
76      public static final String PLEXUS_DEFAULT_HINT = "default";
77  
78      // ----------------------------------------------------------------------
79      // ComponentGleaner Implementation
80      // ----------------------------------------------------------------------
81  
82      public ComponentDescriptor<?> glean( JavaProjectBuilder classCache, JavaClass javaClass )
83          throws ComponentGleanerException
84      {
85          DocletTag tag = javaClass.getTagByName( PLEXUS_COMPONENT_TAG );
86  
87          if ( tag == null )
88          {
89              return null;
90          }
91  
92          Map<String, String> parameters = tag.getNamedParameterMap();
93  
94          // ----------------------------------------------------------------------
95          //
96          // ----------------------------------------------------------------------
97  
98          String fqn = javaClass.getFullyQualifiedName();
99  
100         //log.debug( "Creating descriptor for component: {}", fqn );
101 
102         ComponentDescriptor<?> componentDescriptor = new ComponentDescriptor<Object>();
103 
104         componentDescriptor.setImplementation( fqn );
105 
106         // ----------------------------------------------------------------------
107         // Role
108         // ----------------------------------------------------------------------
109 
110         String role = getParameter( parameters, PLEXUS_ROLE_PARAMETER );
111 
112         if ( role == null )
113         {
114             role = findRole( javaClass );
115 
116             if ( role == null )
117             {
118                 /*
119                 log.warn( "Could not figure out a role for the component '" + fqn + "'. " +
120                     "Please specify a role with a parameter '" + PLEXUS_ROLE_PARAMETER + "' " + "on the @" +
121                     PLEXUS_COMPONENT_TAG + " tag." );
122                     */
123 
124                 return null;
125             }
126         }
127 
128         componentDescriptor.setRole( role );
129 
130         // ----------------------------------------------------------------------
131         // Role hint
132         // ----------------------------------------------------------------------
133 
134         String roleHint = getParameter( parameters, PLEXUS_ROLE_HINT_PARAMETER );
135 
136         if ( roleHint != null )
137         {
138             // getLogger().debug( " Role hint: " + roleHint );
139         }
140 
141         componentDescriptor.setRoleHint( roleHint );
142 
143         // ----------------------------------------------------------------------
144         // Version
145         // ----------------------------------------------------------------------
146 
147         String version = getParameter( parameters, PLEXUS_VERSION_PARAMETER );
148 
149         componentDescriptor.setVersion( version );
150 
151         // ----------------------------------------------------------------------
152         // Lifecycle handler
153         // ----------------------------------------------------------------------
154 
155         String lifecycleHandler = getParameter( parameters, PLEXUS_LIFECYCLE_HANDLER_PARAMETER );
156 
157         componentDescriptor.setLifecycleHandler( lifecycleHandler );
158 
159         // ----------------------------------------------------------------------
160         // Lifecycle handler
161         // ----------------------------------------------------------------------
162 
163         String instatiationStrategy = getParameter( parameters, PLEXUS_INSTANTIATION_STARTEGY_PARAMETER );
164 
165         componentDescriptor.setInstantiationStrategy( instatiationStrategy );
166 
167         // ----------------------------------------------------------------------
168         // Alias
169         // ----------------------------------------------------------------------
170 
171         componentDescriptor.setAlias( getParameter( parameters, PLEXUS_ALIAS_PARAMETER ) );
172 
173         // ----------------------------------------------------------------------
174         //
175         // ----------------------------------------------------------------------
176 
177         findExtraParameters( PLEXUS_COMPONENT_TAG, parameters );
178 
179         // ----------------------------------------------------------------------
180         // Requirements
181         // ----------------------------------------------------------------------
182 
183         findRequirements( classCache, componentDescriptor, javaClass );
184 
185         // ----------------------------------------------------------------------
186         // Description
187         // ----------------------------------------------------------------------
188 
189         String comment = javaClass.getComment();
190 
191         if ( comment != null )
192         {
193             int i = comment.indexOf( '.' );
194 
195             if ( i > 0 )
196             {
197                 comment = comment.substring( 0, i + 1 ); // include the dot
198             }
199         }
200 
201         componentDescriptor.setDescription( comment );
202 
203         // ----------------------------------------------------------------------
204         // Configuration
205         // ----------------------------------------------------------------------
206 
207         XmlPlexusConfiguration configuration = new XmlPlexusConfiguration( "configuration" );
208 
209         findConfiguration( configuration, javaClass );
210 
211         componentDescriptor.setConfiguration( configuration );
212 
213         return componentDescriptor;
214     }
215 
216     // ----------------------------------------------------------------------
217     //
218     // ----------------------------------------------------------------------
219 
220     private final static List<String> IGNORED_INTERFACES = Collections.unmodifiableList( Arrays.asList( new String[]{
221         LogEnabled.class.getName(),
222         Initializable.class.getName(),
223         Configurable.class.getName(),
224         Contextualizable.class.getName(),
225         Disposable.class.getName(),
226         Startable.class.getName(),
227     } ) );
228 
229     private static String getPackage( JavaClass javaClass )
230     {
231         if ( javaClass.getPackage() != null )
232         {
233             return javaClass.getPackage().getName();
234         }
235         else
236         {
237             return "";
238         }
239     }
240 
241     private String findRole( JavaClass javaClass )
242     {
243         // ----------------------------------------------------------------------
244         // Remove any Plexus specific interfaces from the calculation
245         // ----------------------------------------------------------------------
246 
247         List<JavaClass> interfaces = new ArrayList<JavaClass>(  javaClass.getInterfaces() );
248 
249         for ( Iterator<JavaClass> it = interfaces.iterator(); it.hasNext(); )
250         {
251             JavaClass ifc = it.next();
252 
253             if ( IGNORED_INTERFACES.contains( ifc.getFullyQualifiedName() ) )
254             {
255                 it.remove();
256             }
257         }
258 
259         // ----------------------------------------------------------------------
260         // For each implemented interface, check to see if it's a candiate
261         // interface
262         // ----------------------------------------------------------------------
263 
264         String role = null;
265 
266         String className = javaClass.getName();
267 
268         for (JavaClass ifc : interfaces) {
269             String fqn = ifc.getFullyQualifiedName();
270 
271             String pkg = getPackage(ifc);
272 
273             if (pkg == null) {
274                 int index = fqn.lastIndexOf('.');
275 
276                 if (index == -1) {
277                     // -----------------------------------------------------------------------
278                     // This is a special case which will happen in two cases:
279                     // 1) The component is in the default/root package
280                     // 2) The interface is in another build, typically in an -api package
281                     // while the code beeing gleaned in in the -impl build.
282                     //
283                     // Since it's most likely in another package than in the default package
284                     // prepend the gleaned class' package
285                     // -----------------------------------------------------------------------
286 
287                     pkg = getPackage(javaClass);
288 
289                     fqn = pkg + "." + fqn;
290                 } else {
291                     pkg = fqn.substring(0, index);
292                 }
293             }
294 
295             if (fqn == null) {
296                 fqn = ifc.getName();
297             }
298 
299             String name = fqn.substring(pkg.length() + 1);
300 
301             if (className.endsWith(name)) {
302                 if (role != null) {
303                     /*
304                     log.warn( "Found several possible roles for component " + "'" +
305                         javaClass.getFullyQualifiedName() + "', " + "will use '" + role + "', found: " + ifc.getName() + "." );
306                         */
307                 }
308 
309                 role = fqn;
310             }
311         }
312 
313         if ( role == null )
314         {
315             JavaClass superClass = javaClass.getSuperJavaClass();
316 
317             if ( superClass != null )
318             {
319                 role = findRole( superClass );
320             }
321         }
322 
323         return role;
324     }
325 
326     private void findRequirements( JavaProjectBuilder classCache, ComponentDescriptor<?> componentDescriptor,
327                                    JavaClass javaClass )
328     {
329         List<JavaField> fields = javaClass.getFields();
330 
331         // ----------------------------------------------------------------------
332         // Search the super class for requirements
333         // ----------------------------------------------------------------------
334 
335         if ( javaClass.getSuperJavaClass() != null )
336         {
337             findRequirements( classCache, componentDescriptor, javaClass.getSuperJavaClass() );
338         }
339 
340         // ----------------------------------------------------------------------
341         // Search the current class for requirements
342         // ----------------------------------------------------------------------
343 
344         for (JavaField field : fields) {
345             DocletTag tag = field.getTagByName(PLEXUS_REQUIREMENT_TAG);
346 
347             if (tag == null) {
348                 continue;
349             }
350 
351             Map<String, String> parameters = new HashMap<String, String>(tag.getNamedParameterMap());
352 
353             // ----------------------------------------------------------------------
354             // Role
355             // ----------------------------------------------------------------------
356 
357             String requirementClass = field.getType().getFullyQualifiedName();
358 
359             boolean isMap = requirementClass.equals(Map.class.getName()) ||
360                     requirementClass.equals(Collection.class.getName());
361 
362             try {
363                 isMap = isMap || Collection.class.isAssignableFrom(Class.forName(requirementClass));
364             } catch (ClassNotFoundException e) {
365                 // ignore the assignable Collection test, though this should never happen
366             }
367 
368             boolean isList = requirementClass.equals(List.class.getName());
369 
370             ComponentRequirement cr;
371 
372             String hint = getParameter(parameters, PLEXUS_ROLE_HINT_PARAMETER);
373 
374             if (isMap || isList) {
375                 cr = new ComponentRequirementList();
376 
377                 String hintList = getParameter(parameters, PLEXUS_ROLE_HINT_LIST_PARAMETER);
378 
379                 if (hintList != null) {
380                     String[] hintArr = hintList.split(",");
381 
382                     ((ComponentRequirementList) cr).setRoleHints(Arrays.asList(hintArr));
383                 }
384             } else {
385                 cr = new ComponentRequirement();
386 
387                 cr.setRoleHint(hint);
388             }
389 
390             String role = getParameter(parameters, PLEXUS_ROLE_PARAMETER);
391 
392             if (role == null) {
393                 cr.setRole(requirementClass);
394             } else {
395                 cr.setRole(role);
396             }
397 
398             String optional = getParameter(parameters, PLEXUS_OPTIONAL_PARAMETER);
399 
400             cr.setOptional(Boolean.parseBoolean(optional));
401 
402             cr.setFieldName(field.getName());
403 
404             if (isMap || isList) {
405                 if (hint != null) {
406                     /*
407                     log.warn( "Field: '" + field.getName() + "': A role hint cannot be specified if the " +
408                         "field is a java.util.Map or a java.util.List" );
409                         */
410 
411                     continue;
412                 }
413 
414                 if (role == null) {
415                     /*
416                     log.warn( "Field: '" + field.getName() + "': A java.util.Map or java.util.List " +
417                         "requirement has to specify a '" + PLEXUS_ROLE_PARAMETER + "' parameter on " + "the @" +
418                         PLEXUS_REQUIREMENT_TAG + " tag so Plexus can know which components to " +
419                         "put in the map or list." );
420                         */
421 
422                     continue;
423                 }
424 
425                 JavaClass roleClass = classCache.getClassByName(role);
426 
427                 if (role.indexOf('.') == -1 && StringUtils.isEmpty(getPackage(roleClass))) {
428                     role = getPackage(javaClass) + "." + roleClass.getName();
429                 }
430 
431                 cr.setRole(role);
432 
433                 findExtraParameters(PLEXUS_REQUIREMENT_TAG, parameters);
434             }
435 
436             // ----------------------------------------------------------------------
437             //
438             // ----------------------------------------------------------------------
439 
440             componentDescriptor.addRequirement(cr);
441         }
442     }
443 
444     private void findConfiguration( XmlPlexusConfiguration configuration, JavaClass javaClass )
445         throws ComponentGleanerException
446     {
447         List<JavaField> fields = javaClass.getFields();
448 
449         // ----------------------------------------------------------------------
450         // Search the super class for configurable fields.
451         // ----------------------------------------------------------------------
452 
453         if ( javaClass.getSuperJavaClass() != null )
454         {
455             findConfiguration( configuration, javaClass.getSuperJavaClass() );
456         }
457 
458         // ----------------------------------------------------------------------
459         // Search the current class for configurable fields.
460         // ----------------------------------------------------------------------
461 
462         for (JavaField field : fields) {
463             DocletTag tag = field.getTagByName(PLEXUS_CONFIGURATION_TAG);
464 
465             if (tag == null) {
466                 continue;
467             }
468 
469             Map<String, String> parameters = new HashMap<String, String>(tag.getNamedParameterMap());
470 
471             /* don't use the getParameter helper as we like empty strings */
472             String defaultValue = parameters.remove(PLEXUS_DEFAULT_VALUE_PARAMETER);
473 
474             if (defaultValue == null) {
475                 /*
476                 log.warn( "Component: " + javaClass.getName() + ", field name: '" + field.getName() + "': " +
477                     "Currently configurable fields will not be written to the descriptor " +
478                     "without a default value." );*/
479 
480                 continue;
481             }
482 
483             String name = deHump(field.getName());
484 
485             XmlPlexusConfiguration c;
486 
487             c = new XmlPlexusConfiguration(name);
488 
489             c.setValue(defaultValue);
490 
491             //log.debug( " Configuration: {}={}", name, defaultValue );
492 
493             configuration.addChild(c);
494 
495             findExtraParameters(PLEXUS_CONFIGURATION_TAG, parameters);
496         }
497     }
498 
499     // ----------------------------------------------------------------------
500     //
501     // ----------------------------------------------------------------------
502 
503     private void findExtraParameters( String tagName, Map<String, String> parameters )
504     {
505         for (String s : parameters.keySet()) {
506             //log.warn( "Extra parameter on the '" + tagName + "' tag: '" + s + "'." );
507         }
508     }
509 
510     private String getParameter( Map<String, String> parameters, String parameter )
511     {
512         String value = parameters.remove( parameter );
513 
514         if ( StringUtils.isEmpty( value ) )
515         {
516             return null;
517         }
518 
519         return value;
520     }
521 }