| /* |
| * 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.BufferedWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.nio.file.Files; |
| import java.nio.file.Paths; |
| import java.nio.file.StandardOpenOption; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| |
| import javax.annotation.processing.Filer; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.annotation.processing.RoundEnvironment; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.PackageElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.TypeKind; |
| 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 static org.apache.camel.tools.apt.helper.IOHelper.loadText; |
| import static org.apache.camel.tools.apt.helper.Strings.canonicalClassName; |
| import static org.apache.camel.tools.apt.helper.Strings.isNullOrEmpty; |
| |
| /** |
| * Abstract class for Camel apt plugins. |
| */ |
| public final class AnnotationProcessorHelper { |
| |
| private AnnotationProcessorHelper() { |
| } |
| |
| public static String findJavaDoc(Elements elementUtils, Element element, String fieldName, String name, TypeElement classElement, boolean builderPattern) { |
| String answer = null; |
| if (element != null) { |
| answer = elementUtils.getDocComment(element); |
| } |
| if (isNullOrEmpty(answer)) { |
| ExecutableElement setter = findSetter(fieldName, classElement); |
| if (setter != null) { |
| String doc = elementUtils.getDocComment(setter); |
| if (!isNullOrEmpty(doc)) { |
| answer = doc; |
| } |
| } |
| |
| // lets find the getter |
| if (answer == null) { |
| ExecutableElement getter = findGetter(fieldName, classElement); |
| if (getter != null) { |
| String doc = elementUtils.getDocComment(getter); |
| if (!isNullOrEmpty(doc)) { |
| answer = doc; |
| } |
| } |
| } |
| |
| // lets try builder pattern |
| if (answer == null && builderPattern) { |
| List<ExecutableElement> methods = ElementFilter.methodsIn(classElement.getEnclosedElements()); |
| // lets try the builder pattern using annotation name (optional) as the method name |
| if (name != null) { |
| for (ExecutableElement method : methods) { |
| String methodName = method.getSimpleName().toString(); |
| if (name.equals(methodName) && method.getParameters().size() == 1) { |
| String doc = elementUtils.getDocComment(method); |
| if (!isNullOrEmpty(doc)) { |
| answer = doc; |
| break; |
| } |
| } |
| } |
| // there may be builder pattern with no-parameter methods, such as more common for boolean types |
| // so lets try those as well |
| for (ExecutableElement method : methods) { |
| String methodName = method.getSimpleName().toString(); |
| if (name.equals(methodName) && method.getParameters().size() == 0) { |
| String doc = elementUtils.getDocComment(method); |
| if (!isNullOrEmpty(doc)) { |
| answer = doc; |
| break; |
| } |
| } |
| } |
| } |
| // lets try builder pattern using fieldName as the method name |
| for (ExecutableElement method : methods) { |
| String methodName = method.getSimpleName().toString(); |
| if (fieldName.equals(methodName) && method.getParameters().size() == 1) { |
| String doc = elementUtils.getDocComment(method); |
| if (!isNullOrEmpty(doc)) { |
| answer = doc; |
| break; |
| } |
| } |
| } |
| // there may be builder pattern with no-parameter methods, such as more common for boolean types |
| // so lets try those as well |
| for (ExecutableElement method : methods) { |
| String methodName = method.getSimpleName().toString(); |
| if (fieldName.equals(methodName) && method.getParameters().size() == 0) { |
| String doc = elementUtils.getDocComment(method); |
| if (!isNullOrEmpty(doc)) { |
| answer = doc; |
| break; |
| } |
| } |
| } |
| } |
| } |
| return answer; |
| } |
| |
| public static ExecutableElement findSetter(String fieldName, TypeElement classElement) { |
| 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 && method.getReturnType().getKind().equals(TypeKind.VOID)) { |
| return method; |
| } |
| } |
| |
| return null; |
| } |
| |
| public static ExecutableElement findGetter(String fieldName, TypeElement classElement) { |
| String getter1 = "get" + fieldName.substring(0, 1).toUpperCase(); |
| if (fieldName.length() > 1) { |
| getter1 += fieldName.substring(1); |
| } |
| String getter2 = "is" + fieldName.substring(0, 1).toUpperCase(); |
| if (fieldName.length() > 1) { |
| getter2 += fieldName.substring(1); |
| } |
| // lets find the getter |
| List<ExecutableElement> methods = ElementFilter.methodsIn(classElement.getEnclosedElements()); |
| for (ExecutableElement method : methods) { |
| String methodName = method.getSimpleName().toString(); |
| if ((getter1.equals(methodName) || getter2.equals(methodName)) && method.getParameters().size() == 0) { |
| return method; |
| } |
| } |
| |
| return null; |
| } |
| |
| public static VariableElement findFieldElement(TypeElement classElement, String fieldName) { |
| if (isNullOrEmpty(fieldName)) { |
| return null; |
| } |
| |
| List<VariableElement> fields = ElementFilter.fieldsIn(classElement.getEnclosedElements()); |
| for (VariableElement field : fields) { |
| if (fieldName.equals(field.getSimpleName().toString())) { |
| return field; |
| } |
| } |
| |
| return null; |
| } |
| |
| public static TypeElement findTypeElement(ProcessingEnvironment processingEnv, RoundEnvironment roundEnv, String className) { |
| if (isNullOrEmpty(className) || "java.lang.Object".equals(className)) { |
| return null; |
| } |
| |
| Set<? extends Element> rootElements = roundEnv.getRootElements(); |
| for (Element rootElement : rootElements) { |
| if (rootElement instanceof TypeElement) { |
| TypeElement typeElement = (TypeElement) rootElement; |
| String aRootName = canonicalClassName(typeElement.getQualifiedName().toString()); |
| if (className.equals(aRootName)) { |
| return typeElement; |
| } |
| } |
| } |
| |
| // fallback using package name |
| Elements elementUtils = processingEnv.getElementUtils(); |
| |
| int idx = className.lastIndexOf('.'); |
| if (idx > 0) { |
| String packageName = className.substring(0, idx); |
| PackageElement pe = elementUtils.getPackageElement(packageName); |
| if (pe != null) { |
| List<? extends Element> enclosedElements = getEnclosedElements(pe); |
| for (Element rootElement : enclosedElements) { |
| if (rootElement instanceof TypeElement) { |
| TypeElement typeElement = (TypeElement) rootElement; |
| String aRootName = canonicalClassName(typeElement.getQualifiedName().toString()); |
| if (className.equals(aRootName)) { |
| return typeElement; |
| } |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public static List<? extends Element> getEnclosedElements(PackageElement pe) { |
| // some components like hadoop/spark has bad classes that causes javac scanning issues |
| try { |
| return pe.getEnclosedElements(); |
| } catch (Throwable e) { |
| // ignore |
| } |
| return Collections.EMPTY_LIST; |
| } |
| |
| public static void findTypeElementChildren(ProcessingEnvironment processingEnv, RoundEnvironment roundEnv, Set<TypeElement> found, String superClassName) { |
| Elements elementUtils = processingEnv.getElementUtils(); |
| |
| int idx = superClassName.lastIndexOf('.'); |
| if (idx > 0) { |
| String packageName = superClassName.substring(0, idx); |
| PackageElement pe = elementUtils.getPackageElement(packageName); |
| if (pe != null) { |
| List<? extends Element> enclosedElements = pe.getEnclosedElements(); |
| for (Element rootElement : enclosedElements) { |
| if (rootElement instanceof TypeElement) { |
| TypeElement typeElement = (TypeElement) rootElement; |
| String aSuperClassName = canonicalClassName(typeElement.getSuperclass().toString()); |
| if (superClassName.equals(aSuperClassName)) { |
| found.add(typeElement); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public static boolean hasSuperClass(ProcessingEnvironment processingEnv, RoundEnvironment roundEnv, TypeElement classElement, String superClassName) { |
| String aRootName = canonicalClassName(classElement.getQualifiedName().toString()); |
| |
| // do not check the classes from JDK itself |
| if (isNullOrEmpty(aRootName) || aRootName.startsWith("java.") || aRootName.startsWith("javax.")) { |
| return false; |
| } |
| |
| String aSuperClassName = canonicalClassName(classElement.getSuperclass().toString()); |
| if (superClassName.equals(aSuperClassName)) { |
| return true; |
| } |
| |
| TypeElement aSuperClass = findTypeElement(processingEnv, roundEnv, aSuperClassName); |
| if (aSuperClass != null) { |
| return hasSuperClass(processingEnv, roundEnv, aSuperClass, superClassName); |
| } else { |
| return false; |
| } |
| } |
| |
| public static boolean implementsInterface(ProcessingEnvironment processingEnv, RoundEnvironment roundEnv, TypeElement classElement, String interfaceClassName) { |
| while (true) { |
| // check if the class implements the interface |
| List<? extends TypeMirror> list = classElement.getInterfaces(); |
| if (list != null) { |
| for (TypeMirror type : list) { |
| if (type.getKind().compareTo(TypeKind.DECLARED) == 0) { |
| String name = type.toString(); |
| if (interfaceClassName.equals(name)) { |
| return true; |
| } |
| } |
| } |
| } |
| |
| // check super classes which may implement the interface |
| TypeElement baseTypeElement = null; |
| TypeMirror superclass = classElement.getSuperclass(); |
| if (superclass != null) { |
| String superClassName = canonicalClassName(superclass.toString()); |
| baseTypeElement = findTypeElement(processingEnv, roundEnv, superClassName); |
| } |
| if (baseTypeElement != null) { |
| classElement = baseTypeElement; |
| } else { |
| break; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Helper method to produce class output text file using the given handler |
| */ |
| public static void processFile(ProcessingEnvironment processingEnv, String packageName, String fileName, Consumer<PrintWriter> handler) { |
| try { |
| Filer filer = processingEnv.getFiler(); |
| FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, packageName, fileName); |
| log(processingEnv, "Writing file: " + packageName + "/" + fileName); |
| try (Writer w = resource.openWriter(); PrintWriter writer = new PrintWriter(w)) { |
| handler.accept(writer); |
| } |
| } catch (IOException e) { |
| log(processingEnv, e); |
| } |
| } |
| |
| public static void log(ProcessingEnvironment processingEnv, String message) { |
| processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message); |
| } |
| |
| public static void warning(ProcessingEnvironment processingEnv, String message) { |
| processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message); |
| } |
| |
| public static void error(ProcessingEnvironment processingEnv, String message) { |
| processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message); |
| } |
| |
| public static void log(ProcessingEnvironment processingEnv, 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()); |
| } |
| |
| public static String loadResource(ProcessingEnvironment processingEnv, String packageName, String fileName) { |
| Filer filer = processingEnv.getFiler(); |
| |
| FileObject resource; |
| String relativeName = packageName + "/" + fileName; |
| try { |
| resource = filer.getResource(StandardLocation.CLASS_OUTPUT, "", relativeName); |
| } catch (Throwable e) { |
| return "Cannot load classpath resource: " + relativeName + " due: " + e.getMessage(); |
| } |
| |
| if (resource == null) { |
| return null; |
| } |
| |
| try { |
| InputStream is = resource.openInputStream(); |
| return loadText(is, true); |
| } catch (Exception e) { |
| warning(processingEnv, "APT cannot load file: " + packageName + "/" + fileName); |
| } |
| |
| return null; |
| } |
| |
| public static void dumpExceptionToErrorFile(String fileName, String message, Throwable e) { |
| try (BufferedWriter w = Files.newBufferedWriter(Paths.get(fileName), StandardOpenOption.CREATE, StandardOpenOption.APPEND)) { |
| w.append(message); |
| w.append("\n\n"); |
| PrintWriter pw = new PrintWriter(w); |
| e.printStackTrace(pw); |
| pw.flush(); |
| } catch (Throwable t) { |
| // ignore |
| } |
| } |
| } |