| /* |
| * 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.Writer; |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| |
| import javax.annotation.processing.RoundEnvironment; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.AnnotationValue; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.TypeMirror; |
| import javax.tools.Diagnostic; |
| import javax.tools.JavaFileObject; |
| |
| public abstract class AbstractTypeConverterGenerator extends AbstractCamelAnnotationProcessor { |
| |
| public static final class ClassConverters { |
| |
| private final Comparator<TypeMirror> comparator; |
| private final Map<String, Map<TypeMirror, ExecutableElement>> converters = new TreeMap<>(); |
| private final List<ExecutableElement> fallbackConverters = new ArrayList<>(); |
| private int size; |
| private int sizeFallback; |
| private boolean ignoreOnLoadError; |
| |
| ClassConverters(Comparator<TypeMirror> comparator) { |
| this.comparator = comparator; |
| } |
| |
| public boolean isIgnoreOnLoadError() { |
| return ignoreOnLoadError; |
| } |
| |
| void setIgnoreOnLoadError(boolean ignoreOnLoadError) { |
| this.ignoreOnLoadError = ignoreOnLoadError; |
| } |
| |
| void addTypeConverter(TypeMirror to, TypeMirror from, ExecutableElement ee) { |
| converters.computeIfAbsent(toString(to), c -> new TreeMap<>(comparator)).put(from, ee); |
| size++; |
| } |
| |
| void addFallbackTypeConverter(ExecutableElement ee) { |
| fallbackConverters.add(ee); |
| sizeFallback++; |
| } |
| |
| public Map<String, Map<TypeMirror, ExecutableElement>> getConverters() { |
| return converters; |
| } |
| |
| public List<ExecutableElement> getFallbackConverters() { |
| return fallbackConverters; |
| } |
| |
| public long size() { |
| return size; |
| } |
| |
| public long sizeFallback() { |
| return sizeFallback; |
| } |
| |
| public boolean isEmpty() { |
| return size == 0 && sizeFallback == 0; |
| } |
| |
| private static String toString(TypeMirror type) { |
| return type.toString().replaceAll("<.*>", ""); |
| } |
| |
| } |
| |
| @Override |
| protected void doProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) throws Exception { |
| Map<String, ClassConverters> converters = new TreeMap<>(); |
| |
| Comparator<TypeMirror> comparator = (o1, o2) -> processingEnv.getTypeUtils().isAssignable(o1, o2) |
| ? -1 : processingEnv.getTypeUtils().isAssignable(o2, o1) ? +1 : o1.toString().compareTo(o2.toString()); |
| |
| TypeElement converterAnnotationType = this.processingEnv.getElementUtils().getTypeElement("org.apache.camel.Converter"); |
| // the current class with type converters |
| String currentClass = null; |
| boolean ignoreOnLoadError = false; |
| for (Element element : roundEnv.getElementsAnnotatedWith(converterAnnotationType)) { |
| // we need a top level class first |
| if (element.getKind() == ElementKind.CLASS) { |
| TypeElement te = (TypeElement) element; |
| if (!te.getNestingKind().isNested() && acceptClass(te)) { |
| // we only accept top-level classes and if loader is enabled |
| currentClass = te.getQualifiedName().toString(); |
| ignoreOnLoadError = isIgnoreOnLoadError(element); |
| } |
| } else if (currentClass != null && element.getKind() == ElementKind.METHOD) { |
| String key = convertersKey(currentClass); |
| // is the method annotated with @Converter |
| ExecutableElement ee = (ExecutableElement) element; |
| if (isFallbackConverter(ee)) { |
| converters.computeIfAbsent(key, c -> new ClassConverters(comparator)).addFallbackTypeConverter(ee); |
| if (converters.containsKey(key)) { |
| converters.get(key).setIgnoreOnLoadError(ignoreOnLoadError); |
| } |
| } else { |
| TypeMirror to = ee.getReturnType(); |
| TypeMirror from = ee.getParameters().get(0).asType(); |
| String fromStr = toString(from); |
| if (!fromStr.endsWith("[]")) { |
| TypeElement e = this.processingEnv.getElementUtils().getTypeElement(fromStr); |
| if (e != null) { |
| from = e.asType(); |
| } else { |
| processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Could not retrieve type element for " + fromStr); |
| } |
| |
| } |
| converters.computeIfAbsent(key, c -> new ClassConverters(comparator)).addTypeConverter(to, from, ee); |
| if (converters.containsKey(key)) { |
| converters.get(key).setIgnoreOnLoadError(ignoreOnLoadError); |
| } |
| } |
| } |
| } |
| |
| writeConverters(converters); |
| } |
| |
| abstract String convertersKey(String currentClass); |
| |
| abstract void writeConverters(Map<String, ClassConverters> converters) throws Exception; |
| |
| abstract boolean acceptClass(Element element); |
| |
| private static boolean isIgnoreOnLoadError(Element element) { |
| for (AnnotationMirror ann : element.getAnnotationMirrors()) { |
| for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : ann.getElementValues().entrySet()) { |
| if ("ignoreOnLoadError".equals(entry.getKey().getSimpleName().toString())) { |
| return (Boolean) entry.getValue().getValue(); |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isFallbackCanPromote(Element element) { |
| for (AnnotationMirror ann : element.getAnnotationMirrors()) { |
| for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : ann.getElementValues().entrySet()) { |
| if ("fallbackCanPromote".equals(entry.getKey().getSimpleName().toString())) { |
| return (Boolean) entry.getValue().getValue(); |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isAllowNull(Element element) { |
| for (AnnotationMirror ann : element.getAnnotationMirrors()) { |
| for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : ann.getElementValues().entrySet()) { |
| if ("allowNull".equals(entry.getKey().getSimpleName().toString())) { |
| return (Boolean) entry.getValue().getValue(); |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isFallbackConverter(ExecutableElement element) { |
| for (AnnotationMirror ann : element.getAnnotationMirrors()) { |
| for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : ann.getElementValues().entrySet()) { |
| if ("fallback".equals(entry.getKey().getSimpleName().toString())) { |
| return (Boolean) entry.getValue().getValue(); |
| } |
| } |
| } |
| return false; |
| } |
| |
| void writeConverters(String fqn, String suffix, ClassConverters converters) throws Exception { |
| |
| int pos = fqn.lastIndexOf('.'); |
| String p = fqn.substring(0, pos); |
| String c = fqn.substring(pos + 1) + (suffix != null ? suffix : ""); |
| |
| JavaFileObject jfo = processingEnv.getFiler().createSourceFile(p + "." + c); |
| Set<String> converterClasses = new LinkedHashSet<>(); |
| try (Writer writer = jfo.openWriter()) { |
| |
| writer.append("/* Generated by org.apache.camel:apt */\n"); |
| writer.append("package ").append(p).append(";\n"); |
| writer.append("\n"); |
| writer.append("import org.apache.camel.Exchange;\n"); |
| writer.append("import org.apache.camel.TypeConversionException;\n"); |
| writer.append("import org.apache.camel.TypeConverterLoaderException;\n"); |
| writer.append("import org.apache.camel.spi.TypeConverterLoader;\n"); |
| writer.append("import org.apache.camel.spi.TypeConverterRegistry;\n"); |
| writer.append("import org.apache.camel.support.SimpleTypeConverter;\n"); |
| writer.append("import org.apache.camel.support.TypeConverterSupport;\n"); |
| writer.append("import org.apache.camel.util.DoubleMap;\n"); |
| writer.append("\n"); |
| writer.append("/**\n"); |
| writer.append(" * Source code generated by org.apache.camel:apt\n"); |
| writer.append(" */\n"); |
| writer.append("@SuppressWarnings(\"unchecked\")\n"); |
| writer.append("public final class ").append(c).append(" implements TypeConverterLoader {\n"); |
| writer.append("\n"); |
| writer.append(" ").append("public ").append(c).append("() {\n"); |
| writer.append(" }\n"); |
| writer.append("\n"); |
| writer.append(" @Override\n"); |
| writer.append(" public void load(TypeConverterRegistry registry) throws TypeConverterLoaderException {\n"); |
| if (converters.size() > 0) { |
| if (converters.isIgnoreOnLoadError()) { |
| writer.append(" try {\n"); |
| writer.append(" registerConverters(registry);\n"); |
| writer.append(" } catch (Throwable e) {\n"); |
| writer.append(" // ignore on load error\n"); |
| writer.append(" }\n"); |
| } else { |
| writer.append(" registerConverters(registry);\n"); |
| } |
| } |
| if (converters.sizeFallback() > 0) { |
| writer.append(" registerFallbackConverters(registry);\n"); |
| } |
| writer.append(" }\n"); |
| writer.append("\n"); |
| |
| if (converters.size() > 0) { |
| writer.append(" private void registerConverters(TypeConverterRegistry registry) {\n"); |
| for (Map.Entry<String, Map<TypeMirror, ExecutableElement>> to : converters.getConverters().entrySet()) { |
| for (Map.Entry<TypeMirror, ExecutableElement> from : to.getValue().entrySet()) { |
| boolean allowNull = isAllowNull(from.getValue()); |
| writer.append(" addTypeConverter(registry, ").append(to.getKey()).append(".class").append(", ").append(toString(from.getKey())) |
| .append(".class, ").append(Boolean.toString(allowNull)).append(",\n"); |
| writer.append(" (type, exchange, value) -> ").append(toJava(from.getValue(), converterClasses)).append(");\n"); |
| } |
| } |
| writer.append(" }\n"); |
| writer.append("\n"); |
| |
| writer.append( |
| " private static void addTypeConverter(TypeConverterRegistry registry, Class<?> toType, Class<?> fromType, boolean allowNull, SimpleTypeConverter.ConversionMethod method)" |
| + " { \n"); |
| writer.append(" registry.addTypeConverter(toType, fromType, new SimpleTypeConverter(allowNull, method));\n"); |
| writer.append(" }\n"); |
| writer.append("\n"); |
| } |
| |
| if (converters.sizeFallback() > 0) { |
| writer.append(" private void registerFallbackConverters(TypeConverterRegistry registry) {\n"); |
| for (ExecutableElement ee : converters.getFallbackConverters()) { |
| boolean allowNull = isAllowNull(ee); |
| boolean canPromote = isFallbackCanPromote(ee); |
| writer.append(" addFallbackTypeConverter(registry, ") |
| .append(Boolean.toString(allowNull)).append(", ") |
| .append(Boolean.toString(canPromote)).append(", ") |
| .append("(type, exchange, value) -> ") |
| .append(toJavaFallback(ee, converterClasses)) |
| .append(");\n"); |
| } |
| writer.append(" }\n"); |
| writer.append("\n"); |
| |
| writer.append(" private static void addFallbackTypeConverter(TypeConverterRegistry registry, boolean allowNull, boolean canPromote, SimpleTypeConverter.ConversionMethod method) { \n"); |
| writer.append(" registry.addFallbackTypeConverter(new SimpleTypeConverter(allowNull, method), canPromote);\n"); |
| writer.append(" }\n"); |
| writer.append("\n"); |
| } |
| |
| for (String f : converterClasses) { |
| String s = f.substring(f.lastIndexOf('.') + 1); |
| String v = s.substring(0, 1).toLowerCase() + s.substring(1); |
| writer.append(" private volatile ").append(f).append(" ").append(v).append(";\n"); |
| writer.append(" private ").append(f).append(" get").append(s).append("() {\n"); |
| writer.append(" if (").append(v).append(" == null) {\n"); |
| writer.append(" ").append(v).append(" = new ").append(f).append("();\n"); |
| writer.append(" }\n"); |
| writer.append(" return ").append(v).append(";\n"); |
| writer.append(" }\n"); |
| } |
| |
| writer.append("}\n"); |
| writer.flush(); |
| } |
| } |
| |
| private String toString(TypeMirror type) { |
| return type.toString().replaceAll("<.*>", ""); |
| } |
| |
| private String toJava(ExecutableElement converter, Set<String> converterClasses) { |
| String pfx; |
| if (converter.getModifiers().contains(Modifier.STATIC)) { |
| pfx = converter.getEnclosingElement().toString() + "." + converter.getSimpleName(); |
| } else { |
| converterClasses.add(converter.getEnclosingElement().toString()); |
| pfx = "get" + converter.getEnclosingElement().getSimpleName() + "()." + converter.getSimpleName(); |
| } |
| String type = toString(converter.getParameters().get(0).asType()); |
| String cast = type.equals("java.lang.Object") ? "" : "(" + type + ") "; |
| return pfx + "(" + cast + "value" + (converter.getParameters().size() == 2 ? ", exchange" : "") + ")"; |
| } |
| |
| private String toJavaFallback(ExecutableElement converter, Set<String> converterClasses) { |
| String pfx; |
| if (converter.getModifiers().contains(Modifier.STATIC)) { |
| pfx = converter.getEnclosingElement().toString() + "." + converter.getSimpleName(); |
| } else { |
| converterClasses.add(converter.getEnclosingElement().toString()); |
| pfx = "get" + converter.getEnclosingElement().getSimpleName() + "()." + converter.getSimpleName(); |
| } |
| String type = toString(converter.getParameters().get(converter.getParameters().size() - 2).asType()); |
| String cast = type.equals("java.lang.Object") ? "" : "(" + type + ") "; |
| return pfx + "(type, " + (converter.getParameters().size() == 4 ? "exchange, " : "") + cast + "value" + ", registry)"; |
| } |
| |
| } |