blob: b06930d384b8ae80b0872a901fa49c6e51c056dd [file] [log] [blame]
/**
* 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.
*/
package org.apache.camel.tools.apt;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
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.ElementFilter;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriParams;
import org.apache.camel.tools.apt.util.Func1;
import org.apache.camel.tools.apt.util.Strings;
/**
* Processes all Camel endpoints
*/
//@SupportedOptions({"foo"})
@SupportedAnnotationTypes({"org.apache.camel.spi.*"})
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class EndpointAnnotationProcessor extends AbstractProcessor {
public boolean process(Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return true;
}
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(UriEndpoint.class);
for (Element element : elements) {
if (element instanceof TypeElement) {
processEndpointClass(roundEnv, (TypeElement) element);
}
}
return true;
}
protected void processEndpointClass(final RoundEnvironment roundEnv, final TypeElement classElement) {
final UriEndpoint uriEndpoint = classElement.getAnnotation(UriEndpoint.class);
if (uriEndpoint != null) {
String scheme = uriEndpoint.scheme();
if (!Strings.isNullOrEmpty(scheme)) {
String packageName = "org.apache.camel.component";
String fileName = scheme + ".html";
Func1<PrintWriter, Void> handler = new Func1<PrintWriter, Void>() {
@Override
public Void call(PrintWriter writer) {
writeHtmlDocumentation(writer, roundEnv, classElement, uriEndpoint);
return null;
}
};
processFile(packageName, fileName, handler);
}
}
}
protected void writeHtmlDocumentation(PrintWriter writer, RoundEnvironment roundEnv, TypeElement classElement, UriEndpoint uriEndpoint) {
writer.println("<html>");
writer.println("<header>");
String scheme = uriEndpoint.scheme();
String title = scheme + " endpoint";
writer.println("<title>" + "</title>");
writer.println("</header>");
writer.println("<body>");
writer.println("<h1>" + title + "</h1>");
showDocumentationAndFieldInjections(writer, roundEnv, classElement, "");
// This code is not my fault, it seems to honestly be the hacky way to find a class name in APT :)
TypeMirror consumerType = null;
try {
uriEndpoint.consumerClass();
} catch (MirroredTypeException mte) {
consumerType = mte.getTypeMirror();
}
boolean found = false;
String consumerClassName = null;
String consumerPrefix = Strings.getOrElse(uriEndpoint.consumerPrefix(), "");
if (consumerType != null) {
consumerClassName = consumerType.toString();
TypeElement consumerElement = findTypeElement(roundEnv, consumerClassName);
if (consumerElement != null) {
writer.println("<h2>" + scheme + " consumer" + "</h2>");
showDocumentationAndFieldInjections(writer, roundEnv, consumerElement, consumerPrefix);
found = true;
}
}
if (!found && consumerClassName != null) {
warning("APT could not find consumer class " + consumerClassName);
}
writer.println("</body>");
writer.println("</html>");
}
protected void showDocumentationAndFieldInjections(PrintWriter writer, RoundEnvironment roundEnv, TypeElement classElement, String prefix) {
String classDoc = processingEnv.getElementUtils().getDocComment(classElement);
if (!Strings.isNullOrEmpty(classDoc)) {
writer.println("<p>" + classDoc.trim() + "</p>");
}
SortedMap<String, List<String>> sortedMap = new TreeMap<String, List<String>>();
findClassProperties(roundEnv, sortedMap, classElement, prefix);
if (!sortedMap.isEmpty()) {
writer.println("<table class='table'>");
writer.println(" <tr>");
writer.println(" <th>Name</th>");
writer.println(" <th>Type</th>");
writer.println(" <th>Description</th>");
// see defaultValue above
// writer.println(" <th>Default Value</th>");
writer.println(" </tr>");
Set<Map.Entry<String, List<String>>> entries = sortedMap.entrySet();
for (Map.Entry<String, List<String>> entry : entries) {
String name = entry.getKey();
List<String> values = entry.getValue();
writer.println(" <tr>");
writer.println(" <td>" + name + "</td>");
for (String value : values) {
writer.println(value);
}
writer.println(" </tr>");
}
writer.println("</table>");
}
}
protected void findClassProperties(RoundEnvironment roundEnv, SortedMap<String, List<String>> sortedMap, TypeElement classElement, String prefix) {
Elements elementUtils = processingEnv.getElementUtils();
while (true) {
List<VariableElement> fieldElements = ElementFilter.fieldsIn(classElement.getEnclosedElements());
if (fieldElements.isEmpty()) {
break;
}
for (VariableElement fieldElement : fieldElements) {
UriParam param = fieldElement.getAnnotation(UriParam.class);
String fieldName = fieldElement.getSimpleName().toString();
if (param != null) {
String name = param.name();
if (Strings.isNullOrEmpty(name)) {
name = fieldName;
}
name = prefix + name;
// if the field type is a nested parameter then iterate through its fields
TypeMirror fieldType = fieldElement.asType();
String fieldTypeName = fieldType.toString();
TypeElement fieldTypeElement = findTypeElement(roundEnv, fieldTypeName);
UriParams fieldParams = null;
if (fieldTypeElement != null) {
fieldParams = fieldTypeElement.getAnnotation(UriParams.class);
}
if (fieldParams != null) {
String nestedPrefix = prefix;
String extraPrefix = fieldParams.prefix();
if (!Strings.isNullOrEmpty(extraPrefix)) {
nestedPrefix += extraPrefix;
}
findClassProperties(roundEnv, sortedMap, fieldTypeElement, nestedPrefix);
} else {
String docComment = elementUtils.getDocComment(fieldElement);
if (Strings.isNullOrEmpty(docComment)) {
String setter = "set" + fieldName.substring(0, 1).toUpperCase();
if (fieldName.length() > 1) {
setter += fieldName.substring(1);
}
// lets find the setter
List<ExecutableElement> methods = ElementFilter.methodsIn(classElement.getEnclosedElements());
for (ExecutableElement method : methods) {
String methodName = method.getSimpleName().toString();
if (setter.equals(methodName) && method.getParameters().size() == 1) {
String doc = elementUtils.getDocComment(method);
if (!Strings.isNullOrEmpty(doc)) {
docComment = doc;
break;
}
}
}
}
if (docComment == null) {
docComment = "";
}
List<String> values = new ArrayList<String>();
values.add(" <td>" + fieldTypeName + "</td>");
values.add(" <td>" + docComment.trim() + "</td>");
// TODO would be nice here to create a default endpoint/consumer object
// and return the default value of the field so we can put it into the docs
Object defaultValue = null;
if (defaultValue != null) {
values.add(" <td>" + defaultValue + "</td>");
}
if (sortedMap.containsKey(name)) {
error("Duplicate parameter annotation named '" + name + "' on class " + classElement.getQualifiedName());
} else {
sortedMap.put(name, values);
}
}
}
}
TypeElement baseTypeElement = null;
TypeMirror superclass = classElement.getSuperclass();
if (superclass != null) {
baseTypeElement = findTypeElement(roundEnv, superclass.toString());
}
if (baseTypeElement != null) {
classElement = baseTypeElement;
} else {
break;
}
}
}
protected TypeElement findTypeElement(RoundEnvironment roundEnv, String className) {
if (!Strings.isNullOrEmpty(className) && !"java.lang.Object".equals(className)) {
Set<? extends Element> rootElements = roundEnv.getRootElements();
for (Element rootElement : rootElements) {
if (rootElement instanceof TypeElement) {
TypeElement typeElement = (TypeElement) rootElement;
String aRootName = typeElement.getQualifiedName().toString();
if (className.equals(aRootName)) {
return typeElement;
}
}
}
}
return null;
}
/**
* Helper method to produce class output text file using the given handler
*/
protected void processFile(String packageName, String fileName, Func1<PrintWriter, Void> handler) {
PrintWriter writer = null;
try {
Writer out = null;
Filer filer = processingEnv.getFiler();
FileObject resource;
try {
resource = filer.getResource(StandardLocation.CLASS_OUTPUT, packageName, fileName);
} catch (Throwable e) {
//resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "org.apache.camel", "CamelAPT2.txt", rootElements.toArray(new Element[rootElements.size()]));
resource = filer.createResource(StandardLocation.CLASS_OUTPUT, packageName, fileName, new Element[0]);
}
URI uri = resource.toUri();
File file = null;
if (uri != null) {
try {
file = new File(uri);
} catch (Exception e) {
warning("Could not convert output directory resource URI to a file " + e);
}
}
if (file == null) {
warning("No class output directory could be found!");
} else {
file.getParentFile().mkdirs();
out = new FileWriter(file);
writer = new PrintWriter(out);
handler.call(writer);
}
} catch (IOException e) {
log(e);
} finally {
if (writer != null) {
writer.close();
}
}
}
protected void log(String message) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
}
protected void warning(String message) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message);
}
protected void error(String message) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message);
}
protected void log(Throwable e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
StringWriter buffer = new StringWriter();
PrintWriter writer = new PrintWriter(buffer);
e.printStackTrace(writer);
writer.close();
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, buffer.toString());
}
}