| /** |
| * 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()); |
| |
| } |
| } |