blob: a563abec85e0f1fd5564ccc5ff6b743c5d9483e5 [file] [log] [blame]
package org.apache.maven.tools.plugin.extractor.java;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.Type;
import org.apache.maven.plugin.descriptor.InvalidParameterException;
import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.descriptor.Requirement;
import org.apache.maven.project.MavenProject;
import org.apache.maven.tools.plugin.extractor.MojoDescriptorExtractor;
import org.apache.maven.tools.plugin.extractor.ExtractionException;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.StringUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Extracts Mojo descriptors from Java sources.
*
* @todo add example usage tag that can be shown in the doco
* @todo need to add validation directives so that systems embedding maven2 can
* get validation directives to help users in IDEs.
*/
public class JavaMojoDescriptorExtractor
extends AbstractLogEnabled
implements MojoDescriptorExtractor
{
public static final String MAVEN_PLUGIN_INSTANTIATION = "instantiationStrategy";
public static final String CONFIGURATOR = "configurator";
public static final String PARAMETER = "parameter";
public static final String PARAMETER_EXPRESSION = "expression";
public static final String PARAMETER_DEFAULT_VALUE = "default-value";
public static final String PARAMETER_ALIAS = "alias";
public static final String SINCE = "since";
/**
* This defines the default implementation in the case the parameter type is an interface.
*/
public static final String PARAMETER_IMPLEMENTATION = "implementation";
/**
* This indicates the base name of the bean properties used to read/write this parameter's value.
* So:
*
* @parameter property="project"
* <p/>
* Would say there is a getProject() method and a setProject(Project) method. Here the field
* name would not be the basis for the parameter's name. This mode of operation will allow the
* mojos to be usable as beans and will be the promoted form of use.
*/
public static final String PARAMETER_PROPERTY = "property";
public static final String REQUIRED = "required";
public static final String DEPRECATED = "deprecated";
public static final String READONLY = "readonly";
public static final String GOAL = "goal";
public static final String PHASE = "phase";
public static final String EXECUTE = "execute";
public static final String EXECUTE_LIFECYCLE = "lifecycle";
public static final String EXECUTE_PHASE = "phase";
public static final String EXECUTE_GOAL = "goal";
public static final String GOAL_DESCRIPTION = "description";
public static final String GOAL_REQUIRES_DEPENDENCY_RESOLUTION = "requiresDependencyResolution";
public static final String GOAL_REQUIRES_PROJECT = "requiresProject";
public static final String GOAL_REQUIRES_REPORTS = "requiresReports";
public static final String GOAL_IS_AGGREGATOR = "aggregator";
public static final String GOAL_REQUIRES_ONLINE = "requiresOnline";
public static final String GOAL_INHERIT_BY_DEFAULT = "inheritByDefault";
public static final String GOAL_MULTI_EXECUTION_STRATEGY = "attainAlways";
public static final String GOAL_REQUIRES_DIRECT_INVOCATION = "requiresDirectInvocation";
public static final String COMPONENT = "component";
public static final String COMPONENT_ROLE = "role";
public static final String COMPONENT_ROLEHINT = "roleHint";
protected void validateParameter( Parameter parameter, int i )
throws InvalidParameterException
{
// TODO: remove when backward compatibility is no longer an issue.
String name = parameter.getName();
if ( name == null )
{
throw new InvalidParameterException( "name", i );
}
// TODO: remove when backward compatibility is no longer an issue.
String type = parameter.getType();
if ( type == null )
{
throw new InvalidParameterException( "type", i );
}
// TODO: remove when backward compatibility is no longer an issue.
String description = parameter.getDescription();
if ( description == null )
{
throw new InvalidParameterException( "description", i );
}
}
// ----------------------------------------------------------------------
// Mojo descriptor creation from @tags
// ----------------------------------------------------------------------
protected MojoDescriptor createMojoDescriptor( JavaClass javaClass )
throws InvalidPluginDescriptorException
{
MojoDescriptor mojoDescriptor = new MojoDescriptor();
mojoDescriptor.setLanguage( "java" );
mojoDescriptor.setImplementation( javaClass.getFullyQualifiedName() );
mojoDescriptor.setDescription( javaClass.getComment() );
DocletTag tag = findInClassHierarchy( javaClass, MAVEN_PLUGIN_INSTANTIATION );
if ( tag != null )
{
mojoDescriptor.setInstantiationStrategy( tag.getValue() );
}
tag = findInClassHierarchy( javaClass, GOAL_MULTI_EXECUTION_STRATEGY );
if ( tag != null )
{
mojoDescriptor.setExecutionStrategy( MojoDescriptor.MULTI_PASS_EXEC_STRATEGY );
}
else
{
mojoDescriptor.setExecutionStrategy( MojoDescriptor.SINGLE_PASS_EXEC_STRATEGY );
}
// ----------------------------------------------------------------------
// Configurator hint
// ----------------------------------------------------------------------
DocletTag configurator = findInClassHierarchy( javaClass, CONFIGURATOR );
if ( configurator != null )
{
mojoDescriptor.setComponentConfigurator( configurator.getValue() );
}
// ----------------------------------------------------------------------
// Goal name
// ----------------------------------------------------------------------
DocletTag goal = findInClassHierarchy( javaClass, GOAL );
if ( goal != null )
{
mojoDescriptor.setGoal( goal.getValue() );
}
// ----------------------------------------------------------------------
// What version it was introduced in
// ----------------------------------------------------------------------
DocletTag since = findInClassHierarchy( javaClass, SINCE );
if ( since != null )
{
mojoDescriptor.setSince( since.getValue() );
}
// ----------------------------------------------------------------------
// Phase name
// ----------------------------------------------------------------------
DocletTag phase = findInClassHierarchy( javaClass, PHASE );
if ( phase != null )
{
mojoDescriptor.setPhase( phase.getValue() );
}
// ----------------------------------------------------------------------
// Additional phase to execute first
// ----------------------------------------------------------------------
DocletTag execute = findInClassHierarchy( javaClass, EXECUTE );
if ( execute != null )
{
String executePhase = execute.getNamedParameter( EXECUTE_PHASE );
String executeGoal = execute.getNamedParameter( EXECUTE_GOAL );
if ( executePhase == null && executeGoal == null )
{
throw new InvalidPluginDescriptorException( "@execute tag requires a 'phase' or 'goal' parameter" );
}
else if ( executePhase != null && executeGoal != null )
{
throw new InvalidPluginDescriptorException(
"@execute tag can have only one of a 'phase' or 'goal' parameter" );
}
mojoDescriptor.setExecutePhase( executePhase );
mojoDescriptor.setExecuteGoal( executeGoal );
String lifecycle = execute.getNamedParameter( EXECUTE_LIFECYCLE );
if ( lifecycle != null )
{
mojoDescriptor.setExecuteLifecycle( lifecycle );
if ( mojoDescriptor.getExecuteGoal() != null )
{
throw new InvalidPluginDescriptorException(
"@execute lifecycle requires a phase instead of a goal" );
}
}
}
// ----------------------------------------------------------------------
// Dependency resolution flag
// ----------------------------------------------------------------------
DocletTag requiresDependencyResolution = findInClassHierarchy( javaClass, GOAL_REQUIRES_DEPENDENCY_RESOLUTION );
if ( requiresDependencyResolution != null )
{
String value = requiresDependencyResolution.getValue();
if ( value == null || value.length() == 0 )
{
value = "runtime";
}
mojoDescriptor.setDependencyResolutionRequired( value );
}
// ----------------------------------------------------------------------
// Project flag
// ----------------------------------------------------------------------
boolean value = getBooleanTagValue( javaClass, GOAL_REQUIRES_PROJECT, mojoDescriptor.isProjectRequired() );
mojoDescriptor.setProjectRequired( value );
// ----------------------------------------------------------------------
// Aggregator flag
// ----------------------------------------------------------------------
DocletTag aggregator = findInClassHierarchy( javaClass, GOAL_IS_AGGREGATOR );
if ( aggregator != null )
{
mojoDescriptor.setAggregator( true );
}
// ----------------------------------------------------------------------
// requiresDirectInvocation flag
// ----------------------------------------------------------------------
value =
getBooleanTagValue( javaClass, GOAL_REQUIRES_DIRECT_INVOCATION, mojoDescriptor.isDirectInvocationOnly() );
mojoDescriptor.setDirectInvocationOnly( value );
// ----------------------------------------------------------------------
// Online flag
// ----------------------------------------------------------------------
value = getBooleanTagValue( javaClass, GOAL_REQUIRES_ONLINE, mojoDescriptor.isOnlineRequired() );
mojoDescriptor.setOnlineRequired( value );
// ----------------------------------------------------------------------
// inheritByDefault flag
// ----------------------------------------------------------------------
value = getBooleanTagValue( javaClass, GOAL_INHERIT_BY_DEFAULT, mojoDescriptor.isInheritedByDefault() );
mojoDescriptor.setInheritedByDefault( value );
extractParameters( mojoDescriptor, javaClass );
return mojoDescriptor;
}
private static boolean getBooleanTagValue( JavaClass javaClass, String tagName, boolean defaultValue )
{
DocletTag requiresProject = findInClassHierarchy( javaClass, tagName );
if ( requiresProject != null )
{
String requiresProjectValue = requiresProject.getValue();
if ( requiresProjectValue != null && requiresProjectValue.length() > 0 )
{
defaultValue = Boolean.valueOf( requiresProjectValue ).booleanValue();
}
}
return defaultValue;
}
private static DocletTag findInClassHierarchy( JavaClass javaClass, String tagName )
{
DocletTag tag = javaClass.getTagByName( tagName );
if ( tag == null )
{
JavaClass superClass = javaClass.getSuperJavaClass();
if ( superClass != null )
{
tag = findInClassHierarchy( superClass, tagName );
}
}
return tag;
}
private void extractParameters( MojoDescriptor mojoDescriptor, JavaClass javaClass )
throws InvalidPluginDescriptorException
{
// ---------------------------------------------------------------------------------
// We're resolving class-level, ancestor-class-field, local-class-field order here.
// ---------------------------------------------------------------------------------
Map rawParams = extractFieldParameterTags( javaClass );
for ( Iterator it = rawParams.entrySet().iterator(); it.hasNext(); )
{
Map.Entry entry = (Map.Entry) it.next();
JavaField field = (JavaField) entry.getValue();
Type type = field.getType();
Parameter pd = new Parameter();
if ( !type.isArray() )
{
pd.setType( type.getValue() );
}
else
{
StringBuffer value = new StringBuffer( type.getValue() );
int remaining = type.getDimensions();
while ( remaining-- > 0 )
{
value.append( "[]" );
}
pd.setType( value.toString() );
}
pd.setDescription( field.getComment() );
DocletTag componentTag = field.getTagByName( COMPONENT );
if ( componentTag != null )
{
String role = componentTag.getNamedParameter( COMPONENT_ROLE );
if ( role == null )
{
role = field.getType().toString();
}
String roleHint = componentTag.getNamedParameter( COMPONENT_ROLEHINT );
if ( roleHint == null )
{
// support alternate syntax for better compatibility with the Plexus CDC.
roleHint = componentTag.getNamedParameter( "role-hint" );
}
pd.setRequirement( new Requirement( role, roleHint ) );
pd.setName( (String) entry.getKey() );
}
else
{
DocletTag parameter = field.getTagByName( PARAMETER );
// ----------------------------------------------------------------------
// We will look for a property name here first and use that if present
// i.e:
//
// @parameter property="project"
//
// Which will become the name used for the configuration element which
// will in turn will allow plexus to use the corresponding setter.
// ----------------------------------------------------------------------
String property = parameter.getNamedParameter( PARAMETER_PROPERTY );
if ( !StringUtils.isEmpty( property ) )
{
pd.setName( property );
}
else
{
pd.setName( (String) entry.getKey() );
}
pd.setRequired( field.getTagByName( REQUIRED ) != null );
pd.setEditable( field.getTagByName( READONLY ) == null );
DocletTag deprecationTag = field.getTagByName( DEPRECATED );
if ( deprecationTag != null )
{
pd.setDeprecated( deprecationTag.getValue() );
}
DocletTag sinceTag = field.getTagByName( SINCE );
if ( sinceTag != null )
{
pd.setSince( sinceTag.getValue() );
}
String alias = parameter.getNamedParameter( PARAMETER_ALIAS );
if ( !StringUtils.isEmpty( alias ) )
{
pd.setAlias( alias );
}
pd.setExpression( parameter.getNamedParameter( PARAMETER_EXPRESSION ) );
if ( "${reports}".equals( pd.getExpression() ) )
{
mojoDescriptor.setRequiresReports( true );
}
pd.setDefaultValue( parameter.getNamedParameter( PARAMETER_DEFAULT_VALUE ) );
pd.setImplementation( parameter.getNamedParameter( PARAMETER_IMPLEMENTATION ) );
}
mojoDescriptor.addParameter( pd );
}
}
private Map extractFieldParameterTags( JavaClass javaClass )
{
Map rawParams;
// we have to add the parent fields first, so that they will be overwritten by the local fields if
// that actually happens...
JavaClass superClass = javaClass.getSuperJavaClass();
if ( superClass != null )
{
rawParams = extractFieldParameterTags( superClass );
}
else
{
rawParams = new TreeMap();
}
JavaField[] classFields = javaClass.getFields();
if ( classFields != null )
{
for ( int i = 0; i < classFields.length; i++ )
{
JavaField field = classFields[i];
if ( field.getTagByName( PARAMETER ) != null || field.getTagByName( COMPONENT ) != null )
{
rawParams.put( field.getName(), field );
}
}
}
return rawParams;
}
public List execute( MavenProject project, PluginDescriptor pluginDescriptor )
throws ExtractionException, InvalidPluginDescriptorException
{
JavaClass[] javaClasses = discoverClasses( project );
List descriptors = new ArrayList();
for ( int i = 0; i < javaClasses.length; i++ )
{
DocletTag tag = javaClasses[i].getTagByName( GOAL );
if ( tag != null )
{
MojoDescriptor mojoDescriptor = createMojoDescriptor( javaClasses[i] );
mojoDescriptor.setPluginDescriptor( pluginDescriptor );
// Validate the descriptor as best we can before allowing it to be processed.
validate( mojoDescriptor );
descriptors.add( mojoDescriptor );
}
}
return descriptors;
}
protected JavaClass[] discoverClasses( final MavenProject project )
throws ExtractionException
{
JavaDocBuilder builder = new JavaDocBuilder();
for ( Iterator i = project.getCompileSourceRoots().iterator(); i.hasNext(); )
{
builder.addSourceTree( new File( (String) i.next() ) );
}
return builder.getClasses();
}
protected void validate( MojoDescriptor mojoDescriptor )
throws InvalidParameterException
{
List parameters = mojoDescriptor.getParameters();
if ( parameters != null )
{
for ( int j = 0; j < parameters.size(); j++ )
{
validateParameter( (Parameter) parameters.get( j ), j );
}
}
}
}