/*******************************************************************************
*
* Copyright (c) 2007, Dave Whitla
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************/
package au.net.ocean.maven.plugin.extractor;
import au.net.ocean.maven.plugin.annotation.Component;
import au.net.ocean.maven.plugin.annotation.Execution;
import au.net.ocean.maven.plugin.annotation.Mojo;
import au.net.ocean.maven.plugin.annotation.Parameter;
import au.net.ocean.maven.plugin.annotation.Phase;
import au.net.ocean.maven.plugin.annotation.ReadOnly;
import au.net.ocean.maven.plugin.annotation.Required;
import au.net.ocean.maven.plugin.extractor.util.ProjectCompileClassPathLoader;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.descriptor.DuplicateParameterException;
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.PluginDescriptor;
import org.apache.maven.plugin.descriptor.Requirement;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Generate a plugin descriptor by parsing runtime-retained annotations.
* <p/>
*
* @author <a href="mailto:dave.whitla@ocean.net.au">Dave Whitla</a>
* @version $Id: JavaAnnotationMojoDescriptorExtractor.java 0 26/03/2008 15:43:57 dwhitla $
*/
public class JavaAnnotationMojoDescriptorExtractor extends AbstractLogEnabled implements org.apache.maven.tools.plugin.extractor.MojoDescriptorExtractor {
private static final String JAVA_SOURCE_SUFFIX = ".java";
private static final List<String> DIRECTORY_EXCLUDE_PATTERNS = Arrays.asList("CVS",".svn");
private static final String PACKAGE_SEPARATOR = ".";
// TODO: remove when backward compatibility is no longer an issue.
private void validateParameters(MojoDescriptor mojoDescriptor) throws InvalidParameterException {
int parameterIndex = 0;
for (org.apache.maven.plugin.descriptor.Parameter parameter :
(List<org.apache.maven.plugin.descriptor.Parameter>)mojoDescriptor.getParameters()) {
parameterIndex++;
if (parameter.getName() == null) {
throw new InvalidParameterException("name", parameterIndex);
}
if (parameter.getType() == null) {
throw new InvalidParameterException("type", parameterIndex);
}
if (parameter.getDescription() == null) {
throw new InvalidParameterException("description", parameterIndex);
}
}
}
/**
* @inheritDoc
*/
public List execute(MavenProject project, PluginDescriptor pluginDescriptor) throws InvalidPluginDescriptorException {
final List<MojoDescriptor> descriptors = new ArrayList<MojoDescriptor>();
try {
ProjectCompileClassPathLoader compileClassPathLoader = new ProjectCompileClassPathLoader(project, getClass().getClassLoader());
ClassLoader savedContextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(compileClassPathLoader);
for (Object compileSourceRoot : project.getCompileSourceRoots()) {
getLogger().debug("processing source root: " + compileSourceRoot);
descriptors.addAll(extractMojosFromPath(new File(compileSourceRoot.toString()), "", pluginDescriptor));
}
Thread.currentThread().setContextClassLoader(savedContextClassLoader);
} catch (DependencyResolutionRequiredException e) {
getLogger().error(e.getMessage(), e);
}
return descriptors;
}
private Set<MojoDescriptor> extractMojosFromPath(File packageRoot, String packagePrefix, PluginDescriptor pluginDescriptor)
throws InvalidPluginDescriptorException {
Set<MojoDescriptor> mojos = new HashSet<MojoDescriptor>();
for (File file : packageRoot.listFiles()) {
String fileName = file.getName();
if (file.isDirectory() && !DIRECTORY_EXCLUDE_PATTERNS.contains(fileName)) {
getLogger().debug("processing source package: " + packagePrefix + fileName);
String subpackagePrefix = packagePrefix + fileName + PACKAGE_SEPARATOR;
mojos.addAll(extractMojosFromPath(file, subpackagePrefix, pluginDescriptor));
} else if (fileName.endsWith(JAVA_SOURCE_SUFFIX)) {
getLogger().debug("processing source file: " + packagePrefix + fileName);
try {
String className = packagePrefix + fileName.substring(0, fileName.lastIndexOf(JAVA_SOURCE_SUFFIX));
Class<?> clazz = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
MojoDescriptor mojo = extractMojo(clazz);
if (mojo != null) {
mojo.setPluginDescriptor(pluginDescriptor);
mojos.add(mojo);
}
} catch (ClassNotFoundException e) {
getLogger().info(e.toString());
}
}
}
return mojos;
}
private MojoDescriptor extractMojo(Class<?> clazz) throws InvalidPluginDescriptorException {
getLogger().debug("scanning class: " + clazz.getName());
MojoDescriptor mojoDescriptor = null;
Mojo mojo = clazz.getAnnotation(Mojo.class);
if (mojo != null) {
getLogger().info("found @Mojo in: " + clazz.getName());
mojoDescriptor = new MojoDescriptor();
mojoDescriptor.setLanguage("java");
mojoDescriptor.setImplementation(clazz.getName());
mojoDescriptor.setDescription(mojo.description().trim());
mojoDescriptor.setInstantiationStrategy(mojo.instantiationStrategy().value);
mojoDescriptor.setExecutionStrategy(mojo.executionStrategy().value);
String configurator = clazz.getAnnotation(Mojo.class).configurator();
if (configurator.trim().length() > 0) {
mojoDescriptor.setComponentConfigurator(configurator.trim());
}
mojoDescriptor.setGoal(clazz.getAnnotation(Mojo.class).goal().trim());
mojoDescriptor.setPhase(mojo.phase().value);
Execution execute = mojo.execute();
String lifecycle = execute.lifecycle().trim().length() == 0 ? null : execute.lifecycle().trim();
String goal = execute.goal().trim().length() == 0 ? null : execute.goal().trim();
Phase phase = execute.phase();
if (goal != null || phase != Phase.None) {
if (goal != null) {
if (lifecycle != null) {
throw new InvalidPluginDescriptorException("'goal' cannot be specified with 'lifecycle' in execute annotation");
}
if (phase != Phase.None) {
throw new InvalidPluginDescriptorException("execute annotation cannot specify both 'phase' and 'goal'");
}
mojoDescriptor.setExecuteGoal(goal);
} else {
mojoDescriptor.setExecutePhase(phase.value);
if (lifecycle != null) {
mojoDescriptor.setExecuteLifecycle(lifecycle);
}
}
}
mojoDescriptor.setDependencyResolutionRequired(mojo.requiresDependencyResolution().value);
mojoDescriptor.setProjectRequired(mojo.requiresProject());
mojoDescriptor.setAggregator(mojo.aggregator());
mojoDescriptor.setDirectInvocationOnly(mojo.requiresDirectInvocation());
mojoDescriptor.setOnlineRequired(mojo.requiresOnline());
mojoDescriptor.setInheritedByDefault(mojo.inheritByDefault());
if (mojo.deprecated().trim().length() > 0) {
mojoDescriptor.setDeprecated(mojo.deprecated());
}
// extract parameters working back through the inheritancce hierarchy
while (!clazz.equals(Object.class)) {
for (Field field : clazz.getDeclaredFields()) {
org.apache.maven.plugin.descriptor.Parameter parameter = extractParameter(field);
if (parameter != null) {
mojoDescriptor.addParameter(parameter);
if ("${reports}".equals(parameter.getExpression())) {
mojoDescriptor.setRequiresReports(true);
}
}
}
clazz = clazz.getSuperclass();
}
validateParameters(mojoDescriptor);
}
return mojoDescriptor;
}
private org.apache.maven.plugin.descriptor.Parameter extractParameter(Field field) throws DuplicateParameterException {
getLogger().debug("scanning field: " + field.getName());
org.apache.maven.plugin.descriptor.Parameter parameterDescriptor = null;
Parameter parameter = field.getAnnotation(Parameter.class);
Component component = field.getAnnotation(Component.class);
if (parameter != null || component != null) {
Class<?> type = field.getType();
StringBuffer typeNameBuffer = new StringBuffer();
while (type.isArray()) {
typeNameBuffer.append("[]");
type = type.getComponentType();
}
String typeName = typeNameBuffer.insert(0, type.getName()).toString();
parameterDescriptor = new org.apache.maven.plugin.descriptor.Parameter();
parameterDescriptor.setType(typeName);
// component overrides parameter if both are present
if (component != null) {
getLogger().info("found @Component: (" + typeName + "):" + field.getName());
parameterDescriptor.setDescription(component.description());
String role = component.role().trim();
if (role.length() == 0) {
role = typeName;
}
String roleHint = component.roleHint().trim();
if (roleHint.length() == 0) {
roleHint = null;
}
parameterDescriptor.setRequirement(new Requirement(role, roleHint));
parameterDescriptor.setName(field.getName());
} else {
getLogger().info("found @Parameter: (" + typeName + "):" + field.getName());
parameterDescriptor.setDescription(parameter.description());
String property = parameter.property().trim();
parameterDescriptor.setName(property.length() > 0 ? property : field.getName());
parameterDescriptor.setRequired(field.isAnnotationPresent(Required.class));
parameterDescriptor.setEditable(!field.isAnnotationPresent(ReadOnly.class));
// DocletTag deprecationTag = field.getTagByName(DEPRECATED);
// if (deprecationTag != null) {
// parameterDescriptor.setDeprecated(deprecationTag.getValue());
// }
String alias = parameter.alias().trim();
if (alias.length() > 0) {
parameterDescriptor.setAlias(alias);
}
parameterDescriptor.setExpression(parameter.expression().trim());
parameterDescriptor.setDefaultValue(parameter.defaultValue());
}
}
return parameterDescriptor;
}
}