package org.codehaus.plexus.metadata.gleaner; /* * The MIT License * * Copyright (c) 2004, The Codehaus * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.codehaus.plexus.component.repository.ComponentDescriptor; import org.codehaus.plexus.component.repository.ComponentRequirement; import org.codehaus.plexus.component.repository.ComponentRequirementList; import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration; import org.codehaus.plexus.logging.LogEnabled; import org.codehaus.plexus.personality.plexus.lifecycle.phase.Configurable; import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable; import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable; import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable; import org.codehaus.plexus.personality.plexus.lifecycle.phase.Startable; import org.codehaus.plexus.util.StringUtils; import com.thoughtworks.qdox.model.DocletTag; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaClassCache; import com.thoughtworks.qdox.model.JavaField; /** * A source component gleaner which uses QDox to discover Javadoc annotations. * * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a> * @version $Id$ */ public class QDoxComponentGleaner extends ComponentGleanerSupport implements SourceComponentGleaner { public static final String PLEXUS_COMPONENT_TAG = "plexus.component"; public static final String PLEXUS_REQUIREMENT_TAG = "plexus.requirement"; public static final String PLEXUS_CONFIGURATION_TAG = "plexus.configuration"; public static final String PLEXUS_VERSION_PARAMETER = "version"; public static final String PLEXUS_ROLE_PARAMETER = "role"; public static final String PLEXUS_ROLE_HINT_PARAMETER = "role-hint"; public static final String PLEXUS_ROLE_HINT_LIST_PARAMETER = "role-hints"; public static final String PLEXUS_ALIAS_PARAMETER = "alias"; public static final String PLEXUS_DEFAULT_VALUE_PARAMETER = "default-value"; public static final String PLEXUS_LIFECYCLE_HANDLER_PARAMETER = "lifecycle-handler"; public static final String PLEXUS_INSTANTIATION_STARTEGY_PARAMETER = "instantiation-strategy"; public static final String PLEXUS_DEFAULT_HINT = "default"; // ---------------------------------------------------------------------- // ComponentGleaner Implementation // ---------------------------------------------------------------------- public ComponentDescriptor<?> glean( JavaClassCache classCache, JavaClass javaClass ) throws ComponentGleanerException { DocletTag tag = javaClass.getTagByName( PLEXUS_COMPONENT_TAG ); if ( tag == null ) { return null; } Map<String, String> parameters = tag.getNamedParameterMap(); // ---------------------------------------------------------------------- // // ---------------------------------------------------------------------- String fqn = javaClass.getFullyQualifiedName(); //log.debug( "Creating descriptor for component: {}", fqn ); ComponentDescriptor<?> componentDescriptor = new ComponentDescriptor<Object>(); componentDescriptor.setImplementation( fqn ); // ---------------------------------------------------------------------- // Role // ---------------------------------------------------------------------- String role = getParameter( parameters, PLEXUS_ROLE_PARAMETER ); if ( role == null ) { role = findRole( javaClass ); if ( role == null ) { /* log.warn( "Could not figure out a role for the component '" + fqn + "'. " + "Please specify a role with a parameter '" + PLEXUS_ROLE_PARAMETER + "' " + "on the @" + PLEXUS_COMPONENT_TAG + " tag." ); */ return null; } } componentDescriptor.setRole( role ); // ---------------------------------------------------------------------- // Role hint // ---------------------------------------------------------------------- String roleHint = getParameter( parameters, PLEXUS_ROLE_HINT_PARAMETER ); if ( roleHint != null ) { // getLogger().debug( " Role hint: " + roleHint ); } componentDescriptor.setRoleHint( roleHint ); // ---------------------------------------------------------------------- // Version // ---------------------------------------------------------------------- String version = getParameter( parameters, PLEXUS_VERSION_PARAMETER ); componentDescriptor.setVersion( version ); // ---------------------------------------------------------------------- // Lifecycle handler // ---------------------------------------------------------------------- String lifecycleHandler = getParameter( parameters, PLEXUS_LIFECYCLE_HANDLER_PARAMETER ); componentDescriptor.setLifecycleHandler( lifecycleHandler ); // ---------------------------------------------------------------------- // Lifecycle handler // ---------------------------------------------------------------------- String instatiationStrategy = getParameter( parameters, PLEXUS_INSTANTIATION_STARTEGY_PARAMETER ); componentDescriptor.setInstantiationStrategy( instatiationStrategy ); // ---------------------------------------------------------------------- // Alias // ---------------------------------------------------------------------- componentDescriptor.setAlias( getParameter( parameters, PLEXUS_ALIAS_PARAMETER ) ); // ---------------------------------------------------------------------- // // ---------------------------------------------------------------------- findExtraParameters( PLEXUS_COMPONENT_TAG, parameters ); // ---------------------------------------------------------------------- // Requirements // ---------------------------------------------------------------------- findRequirements( classCache, componentDescriptor, javaClass ); // ---------------------------------------------------------------------- // Description // ---------------------------------------------------------------------- String comment = javaClass.getComment(); if ( comment != null ) { int i = comment.indexOf( '.' ); if ( i > 0 ) { comment = comment.substring( 0, i + 1 ); // include the dot } } componentDescriptor.setDescription( comment ); // ---------------------------------------------------------------------- // Configuration // ---------------------------------------------------------------------- XmlPlexusConfiguration configuration = new XmlPlexusConfiguration( "configuration" ); findConfiguration( configuration, javaClass ); componentDescriptor.setConfiguration( configuration ); return componentDescriptor; } // ---------------------------------------------------------------------- // // ---------------------------------------------------------------------- private final static List<String> IGNORED_INTERFACES = Collections.unmodifiableList( Arrays.asList( new String[]{ LogEnabled.class.getName(), Initializable.class.getName(), Configurable.class.getName(), Contextualizable.class.getName(), Disposable.class.getName(), Startable.class.getName(), } ) ); private String findRole( JavaClass javaClass ) { // ---------------------------------------------------------------------- // Remove any Plexus specific interfaces from the calculation // ---------------------------------------------------------------------- List<JavaClass> interfaces = new ArrayList<JavaClass>( Arrays.asList( javaClass.getImplementedInterfaces() ) ); for ( Iterator<JavaClass> it = interfaces.iterator(); it.hasNext(); ) { JavaClass ifc = it.next(); if ( IGNORED_INTERFACES.contains( ifc.getFullyQualifiedName() ) ) { it.remove(); } } // ---------------------------------------------------------------------- // For each implemented interface, check to see if it's a candiate // interface // ---------------------------------------------------------------------- String role = null; String className = javaClass.getName(); for ( Iterator<JavaClass> it = interfaces.iterator(); it.hasNext(); ) { JavaClass ifc = it.next(); String fqn = ifc.getFullyQualifiedName(); String pkg = ifc.getPackage(); if ( pkg == null ) { int index = fqn.lastIndexOf( '.' ); if ( index == -1 ) { // ----------------------------------------------------------------------- // This is a special case which will happen in two cases: // 1) The component is in the default/root package // 2) The interface is in another build, typically in an -api package // while the code beeing gleaned in in the -impl build. // // Since it's most likely in another package than in the default package // prepend the gleaned class' package // ----------------------------------------------------------------------- pkg = javaClass.getPackage(); fqn = pkg + "." + fqn; } else { pkg = fqn.substring( 0, index ); } } if ( fqn == null ) { fqn = ifc.getName(); } String name = fqn.substring( pkg.length() + 1 ); if ( className.endsWith( name ) ) { if ( role != null ) { /* log.warn( "Found several possible roles for component " + "'" + javaClass.getFullyQualifiedName() + "', " + "will use '" + role + "', found: " + ifc.getName() + "." ); */ } role = fqn; } } if ( role == null ) { JavaClass superClass = javaClass.getSuperJavaClass(); if ( superClass != null ) { role = findRole( superClass ); } } return role; } private void findRequirements( JavaClassCache classCache, ComponentDescriptor<?> componentDescriptor, JavaClass javaClass ) { JavaField[] fields = javaClass.getFields(); // ---------------------------------------------------------------------- // Search the super class for requirements // ---------------------------------------------------------------------- if ( javaClass.getSuperJavaClass() != null ) { findRequirements( classCache, componentDescriptor, javaClass.getSuperJavaClass() ); } // ---------------------------------------------------------------------- // Search the current class for requirements // ---------------------------------------------------------------------- for ( int i = 0; i < fields.length; i++ ) { JavaField field = fields[i]; DocletTag tag = field.getTagByName( PLEXUS_REQUIREMENT_TAG ); if ( tag == null ) { continue; } Map<String, String> parameters = new HashMap<String, String>( tag.getNamedParameterMap() ); // ---------------------------------------------------------------------- // Role // ---------------------------------------------------------------------- String requirementClass = field.getType().getJavaClass().getFullyQualifiedName(); boolean isMap = requirementClass.equals( Map.class.getName() ) || requirementClass.equals( Collection.class.getName() ); try { isMap = isMap || Collection.class.isAssignableFrom( Class.forName( requirementClass ) ); } catch ( ClassNotFoundException e ) { // ignore the assignable Collection test, though this should never happen } boolean isList = requirementClass.equals( List.class.getName() ); ComponentRequirement cr; String hint = getParameter( parameters, PLEXUS_ROLE_HINT_PARAMETER ); if ( isMap || isList ) { cr = new ComponentRequirementList(); String hintList = getParameter( parameters, PLEXUS_ROLE_HINT_LIST_PARAMETER ); if ( hintList != null ) { String[] hintArr = hintList.split( "," ); ( (ComponentRequirementList) cr).setRoleHints( Arrays.asList( hintArr ) ); } } else { cr = new ComponentRequirement(); cr.setRoleHint( hint ); } String role = getParameter( parameters, PLEXUS_ROLE_PARAMETER ); if ( role == null ) { cr.setRole( requirementClass ); } else { cr.setRole( role ); } cr.setFieldName( field.getName() ); if ( isMap || isList ) { if ( hint != null ) { /* log.warn( "Field: '" + field.getName() + "': A role hint cannot be specified if the " + "field is a java.util.Map or a java.util.List" ); */ continue; } if ( role == null ) { /* log.warn( "Field: '" + field.getName() + "': A java.util.Map or java.util.List " + "requirement has to specify a '" + PLEXUS_ROLE_PARAMETER + "' parameter on " + "the @" + PLEXUS_REQUIREMENT_TAG + " tag so Plexus can know which components to " + "put in the map or list." ); */ continue; } JavaClass roleClass = classCache.getClassByName( role ); if ( role.indexOf( '.' ) == -1 && StringUtils.isEmpty( roleClass.getPackage() ) ) { role = javaClass.getPackage() + "." + roleClass.getName(); } cr.setRole( role ); findExtraParameters( PLEXUS_REQUIREMENT_TAG, parameters ); } // ---------------------------------------------------------------------- // // ---------------------------------------------------------------------- componentDescriptor.addRequirement( cr ); } } private void findConfiguration( XmlPlexusConfiguration configuration, JavaClass javaClass ) throws ComponentGleanerException { JavaField[] fields = javaClass.getFields(); // ---------------------------------------------------------------------- // Search the super class for configurable fields. // ---------------------------------------------------------------------- if ( javaClass.getSuperJavaClass() != null ) { findConfiguration( configuration, javaClass.getSuperJavaClass() ); } // ---------------------------------------------------------------------- // Search the current class for configurable fields. // ---------------------------------------------------------------------- for ( int i = 0; i < fields.length; i++ ) { JavaField field = fields[i]; DocletTag tag = field.getTagByName( PLEXUS_CONFIGURATION_TAG ); if ( tag == null ) { continue; } Map<String, String> parameters = new HashMap<String, String>( tag.getNamedParameterMap() ); /* don't use the getParameter helper as we like empty strings */ String defaultValue = parameters.remove( PLEXUS_DEFAULT_VALUE_PARAMETER ); if ( defaultValue == null ) { /* log.warn( "Component: " + javaClass.getName() + ", field name: '" + field.getName() + "': " + "Currently configurable fields will not be written to the descriptor " + "without a default value." );*/ continue; } String name = deHump( field.getName() ); XmlPlexusConfiguration c; c = new XmlPlexusConfiguration( name ); c.setValue( defaultValue ); //log.debug( " Configuration: {}={}", name, defaultValue ); configuration.addChild( c ); findExtraParameters( PLEXUS_CONFIGURATION_TAG, parameters ); } } // ---------------------------------------------------------------------- // // ---------------------------------------------------------------------- private void findExtraParameters( String tagName, Map<String, String> parameters ) { for ( Iterator<String> it = parameters.keySet().iterator(); it.hasNext(); ) { String s = it.next(); //log.warn( "Extra parameter on the '" + tagName + "' tag: '" + s + "'." ); } } private String getParameter( Map<String, String> parameters, String parameter ) { String value = parameters.remove( parameter ); if ( StringUtils.isEmpty( value ) ) { return null; } return value; } }