blob: dc912a7a02ad544814aec318f23f8f063379f377 [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.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)";
}
}