blob: 5bbd8adf7e9d43a4c8d5d6145142ba24d9ec53ea [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 com.alibaba.dubbo.config.async;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@SupportedAnnotationTypes({"com.alibaba.dubbo.config.async.DubboAsync"})
public class AsyncAnnotationProcessor extends AbstractProcessor {
private static final String OBJECT_NAME = "java.lang.Object";
private static final String FUTURE_NAME = "CompletableFuture";
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) return false;
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(DubboAsync.class);
if (elements == null || elements.isEmpty()) return false;
for (Element element : elements) {
if (element.getKind() == ElementKind.INTERFACE) {
generateAsyncInterface((TypeElement) element);
}
}
return false;
}
private void generateAsyncInterface(TypeElement element) {
Name qualified = element.getQualifiedName();
if (qualified == null) return;
String qualifiedName = qualified.toString();
if (qualifiedName.length() == 0) return;
String className = element.getSimpleName().toString();
PackageElement packageElement = (PackageElement) element.getEnclosingElement();
String packageName = packageElement.getQualifiedName().toString();
StringBuilder result = new StringBuilder();
startAsyncInterface(result, qualifiedName, className, packageName);
generateAsyncMethods(result, element);
endAsyncInterface(result);
Writer writer = null;
try {
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(qualifiedName + "Async");
if (sourceFile.getLastModified() > 0) return;
writer = sourceFile.openWriter();
writer.write(result.toString());
writer.close();
} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Failed to generate async interface.");
} finally {
close(writer);
}
}
private void generateAsyncMethods(StringBuilder result, TypeElement typeElement) {
List<? extends Element> elements = typeElement.getEnclosedElements();
Set<String> existMethods = extractExistMethods(elements);
for (Element element : elements) {
if (element.getKind() != ElementKind.METHOD) continue;
generateAsyncMethod(result, (ExecutableElement) element, existMethods);
}
}
private Set<String> extractExistMethods(List<? extends Element> elements) {
Set<String> result = new HashSet<String>();
for (Element element : elements) {
result.add(element.getSimpleName().toString());
}
return result;
}
private void generateAsyncMethod(StringBuilder result, ExecutableElement element, Set<String> existMethods) {
String asyncMethodName = element.getSimpleName().toString() + "Async";
if (existMethods.contains(asyncMethodName)) return;
appendTypeParameters(result, element);
appendReturnType(result, element);
result.append(asyncMethodName).append('(');
appendParameters(result, element);
result.append(");");
}
// generic parameter type
private void appendTypeParameters(StringBuilder result, ExecutableElement element) {
List<? extends TypeParameterElement> typeParameters = element.getTypeParameters();
if (typeParameters == null || typeParameters.size() == 0) return;
result.append("<");
int i = 1;
for (TypeParameterElement typeParameter : typeParameters) {
result.append(typeParameter.getSimpleName().toString());
appendBounds(result, typeParameter);
if (i++ < typeParameters.size()) result.append(",");
}
result.append("> ");
}
// return type
private void appendReturnType(StringBuilder result, ExecutableElement element) {
TypeMirror returnType = element.getReturnType();
if (returnType.getKind() == TypeKind.VOID) {
result.append(FUTURE_NAME).append(" ");
} else {
result.append(FUTURE_NAME).append('<').append(type2ReturnName(returnType)).append("> ");
}
}
// parameters
private void appendParameters(StringBuilder result, ExecutableElement method) {
List<? extends VariableElement> parameters = method.getParameters();
int i = 1;
for (VariableElement element : parameters) {
result.append(type2ParameterName(element.asType())).append(" ").append(element.getSimpleName().toString());
if (i++ < parameters.size()) {
result.append(",");
}
}
}
private void appendBounds(StringBuilder result, TypeParameterElement typeParameter) {
List<? extends TypeMirror> bounds = typeParameter.getBounds();
if (bounds == null || bounds.size() == 0) return;
if (bounds.size() == 1) {
String boundName = type2ReturnName(bounds.get(0));
if (OBJECT_NAME.equals(boundName)) return;
}
result.append(" extends ");
int i = 1;
for (TypeMirror bound : bounds) {
String boundName = type2ReturnName(bound);
result.append(boundName);
if (i < bounds.size()) result.append(",");
}
}
private String type2ReturnName(TypeMirror typeMirror) {
TypeKind kind = typeMirror.getKind();
if (kind == TypeKind.VOID) return "";
if (kind == TypeKind.ARRAY) {
ArrayType arrayType = (ArrayType) typeMirror;
TypeMirror componentType = arrayType.getComponentType();
return type2ReturnName(componentType) + "[]";
}
if (kind == TypeKind.BOOLEAN) {
return "Boolean";
}
if (kind == TypeKind.BYTE) {
return "Byte";
}
if (kind == TypeKind.CHAR) {
return "Char";
}
if (kind == TypeKind.DOUBLE) {
return "Double";
}
if (kind == TypeKind.FLOAT) {
return "Float";
}
if (kind == TypeKind.INT) {
return "Integer";
}
if (kind == TypeKind.LONG) {
return "Long";
}
if (kind == TypeKind.SHORT) {
return "Short";
}
if (kind == TypeKind.DECLARED) {
DeclaredType declaredType = (DeclaredType) typeMirror;
Element element = declaredType.asElement();
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (typeArguments == null || typeArguments.size() == 0) {
return typeName(element);
}
StringBuilder result = new StringBuilder();
result.append(typeName(element)).append("<");
int i = 1;
for (TypeMirror typeArgument : typeArguments) {
result.append(type2ReturnName(typeArgument));
if (i++ < typeArguments.size()) result.append(",");
}
result.append(">");
return result.toString();
}
if (kind == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) typeMirror;
return typeVariable.asElement().getSimpleName().toString();
}
if (kind == TypeKind.WILDCARD) {
WildcardType wildcardType = (WildcardType) typeMirror;
StringBuilder result = new StringBuilder();
result.append("?");
appendSuperBound(result, wildcardType.getSuperBound());
appendExtendsBound(result, wildcardType.getExtendsBound());
return result.toString();
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, kind.toString());
return "";
}
private void appendExtendsBound(StringBuilder resut, TypeMirror extendsBound) {
if (extendsBound == null) return;
String name = type2ReturnName(extendsBound);
if (name.equals(OBJECT_NAME)) return;
resut.append(" extends ").append(name);
}
private void appendSuperBound(StringBuilder result, TypeMirror superBound) {
if (superBound == null) return;
String name = type2ReturnName(superBound);
if (name.equals(OBJECT_NAME)) return;
result.append(" super ").append(name);
}
private String type2ParameterName(TypeMirror typeMirror) {
TypeKind kind = typeMirror.getKind();
if (kind == TypeKind.BOOLEAN) {
return "boolean";
}
if (kind == TypeKind.BYTE) {
return "byte";
}
if (kind == TypeKind.CHAR) {
return "char";
}
if (kind == TypeKind.DOUBLE) {
return "double";
}
if (kind == TypeKind.FLOAT) {
return "float";
}
if (kind == TypeKind.INT) {
return "int";
}
if (kind == TypeKind.LONG) {
return "long";
}
if (kind == TypeKind.SHORT) {
return "short";
}
return type2ReturnName(typeMirror);
}
private String typeName(Element element) {
if (element.getKind() == ElementKind.CLASS
|| element.getKind() == ElementKind.INTERFACE
|| element.getKind() == ElementKind.ENUM) {
return ((TypeElement) element).getQualifiedName().toString();
}
return element.getSimpleName().toString();
}
private void endAsyncInterface(StringBuilder result) {
result.append("\n}");
}
private void startAsyncInterface(StringBuilder result, String qualifiedName, String className, String packageName) {
result.append("package ").append(packageName).append(";\n");
result.append("import java.util.concurrent.CompletableFuture;\n");
result.append("@javax.annotation.Generated(\"com.alibaba.dubbo.async.processor.AsyncAnnotationProcessor\")\n");
result.append("@org.apache.dubbo.common.config.AsyncFor(").append(qualifiedName).append(".class)\n");
result.append("public interface ").append(className).append("Async extends ").append(className).append(" {\n");
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
private void close(Writer writer) {
if (writer == null) return;
try {
writer.close();
} catch (IOException e) {
}
}
}