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