package io.takari.maven.plugins.plugin; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.MirroredTypeException; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; import javax.tools.StandardLocation; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import io.takari.maven.plugins.plugin.model.MojoDescriptor; import io.takari.maven.plugins.plugin.model.MojoParameter; import io.takari.maven.plugins.plugin.model.MojoRequirement; import io.takari.maven.plugins.plugin.model.PluginDescriptor; import io.takari.maven.plugins.plugin.model.io.xpp3.PluginDescriptorXpp3Writer; /** * @TODO access javadoc tag text, like @deprecated and @since */ @SupportedSourceVersion(SourceVersion.RELEASE_7) public class MojoDescriptorGleaner extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element element : roundEnv.getRootElements()) { try { writeMojoXml(processType((TypeElement) element), element); } catch (IOException e) { // TODO it'd be nice to capture IOException somewhere too processingEnv.getMessager().printMessage(Kind.ERROR, "Could not create aggregate output " + e.getMessage()); } } return false; // don't claim the annotations, other processors are welcome to use them } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new HashSet<>(); types.add(Mojo.class.getName()); types.add(Parameter.class.getName()); types.add(Component.class.getName()); return types; } MojoDescriptor processType(TypeElement type) { MojoDescriptor descriptor = new MojoDescriptor(); descriptor.setImplementation(type.getQualifiedName().toString()); descriptor.setSuperclasses(getSuperclasses(type, new ArrayList<String>())); Mojo mojo = type.getAnnotation(Mojo.class); if (mojo != null) { // mojo is null for classes that have @Parameter/@Component element annotations descriptor.setLanguage("java"); descriptor.setGoal(mojo.name()); descriptor.setExecutionStrategy(mojo.executionStrategy()); descriptor.setRequiresProject(mojo.requiresProject()); descriptor.setRequiresReports(mojo.requiresReports()); descriptor.setAggregator(mojo.aggregator()); descriptor.setRequiresDirectInvocation(mojo.requiresDirectInvocation()); descriptor.setRequiresOnline(mojo.requiresOnline()); descriptor.setInheritedByDefault(mojo.inheritByDefault()); if (!isEmpty(mojo.configurator())) { descriptor.setConfigurator(mojo.configurator()); } descriptor.setThreadSafe(mojo.threadSafe()); descriptor.setPhase(mojo.defaultPhase().id()); descriptor.setRequiresDependencyResolution(mojo.requiresDependencyResolution().id()); descriptor.setRequiresDependencyCollection(mojo.requiresDependencyCollection().id()); descriptor.setInstantiationStrategy(mojo.instantiationStrategy().id()); if (getElementUtils().isDeprecated(type)) { descriptor.setDeprecated("No reason given"); // TODO parse javadoc } descriptor.setDescription(getDescription(type)); } processTypeFields(type, descriptor); Sorting.sortParameters(descriptor.getParameters()); Sorting.sortRequirements(descriptor.getRequirements()); return descriptor; } private void processTypeFields(TypeElement type, MojoDescriptor descriptor) { // non-static fields for (Element member : type.getEnclosedElements()) { if (member instanceof VariableElement) { Parameter parameter = member.getAnnotation(Parameter.class); Component component = member.getAnnotation(Component.class); if (parameter != null && component != null) { // TODO error marker } if (parameter != null) { descriptor.addParameter(toParameterDescriptor((VariableElement) member, parameter)); } else if (component != null) { descriptor.addRequirement(toComponentDescriptor((VariableElement) member, component)); } } } } private MojoRequirement toComponentDescriptor(VariableElement field, Component component) { MojoRequirement result = new MojoRequirement(); result.setFieldName(field.getSimpleName().toString()); result.setRole(getComponentRole(field, component)); result.setRoleHint(component.hint()); return result; } private String getComponentRole(VariableElement field, Component component) { String role; try { role = component.role().getName(); } catch (MirroredTypeException e) { role = e.getTypeMirror().toString(); } if (!Object.class.getName().equals(role)) { return role; } return getTypeString(field.asType()); } private String getTypeString(TypeMirror type) { TypeElement typeElement = (TypeElement) getTypeUtils().asElement(type); if (typeElement != null) { // this returns raw parameterized types return typeElement.getQualifiedName().toString(); } // this deals with primitive and array types return type.toString(); } private MojoParameter toParameterDescriptor(VariableElement field, Parameter parameter) { MojoParameter result = new MojoParameter(); result.setName(field.getSimpleName().toString()); if (!isEmpty(parameter.alias())) { result.setAlias(parameter.alias()); } if (!isEmpty(parameter.defaultValue())) { result.setDefaultValue(parameter.defaultValue()); } if (!isEmpty(parameter.property())) { result.setExpression("${" + parameter.property() + "}"); } result.setEditable(!parameter.readonly()); result.setRequired(parameter.required()); result.setType(getTypeString(field.asType())); result.setDescription(getDescription(field)); return result; } private List<String> getSuperclasses(TypeElement type, List<String> superclasses) { TypeElement superclass = type; while ((superclass = (TypeElement) getTypeUtils().asElement(superclass.getSuperclass())) != null) { String qualifiedName = superclass.getQualifiedName().toString(); if ("java.lang.Object".equals(qualifiedName)) { break; } superclasses.add(qualifiedName); } return superclasses; } private Types getTypeUtils() { return processingEnv.getTypeUtils(); } private String getDescription(Element element) { String description = getElementUtils().getDocComment(element); if (description != null) { description = description.trim(); } return !isEmpty(description) ? description : null; } private Elements getElementUtils() { return processingEnv.getElementUtils(); } private static boolean isEmpty(String str) { return str == null || str.isEmpty(); } void writeMojoXml(MojoDescriptor descriptor, Element... elements) throws IOException { if (descriptor.getGoal() == null && descriptor.getParameters().isEmpty() && descriptor.getRequirements().isEmpty()) { // roundEnv.getRootElements() returns all classes, regardless of #getSupportedAnnotationTypes // TODO need to investigate if this is a bug or a feature return; } PluginDescriptor mojos = new PluginDescriptor(); mojos.addMojo(descriptor); FileObject output = processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", descriptor.getImplementation() + ".mojo.xml", elements); try (OutputStream os = output.openOutputStream()) { new PluginDescriptorXpp3Writer().write(os, mojos); } } }