com.gh.bmd.jrt.processor.RoutineProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jroutine-processor Show documentation
Show all versions of jroutine-processor Show documentation
Parallel programming on the go
/*
* Licensed 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.gh.bmd.jrt.processor;
import com.gh.bmd.jrt.annotation.Bind;
import com.gh.bmd.jrt.annotation.Pass;
import com.gh.bmd.jrt.annotation.Pass.PassingMode;
import com.gh.bmd.jrt.annotation.Share;
import com.gh.bmd.jrt.annotation.Timeout;
import com.gh.bmd.jrt.builder.RoutineConfiguration.OrderType;
import com.gh.bmd.jrt.builder.RoutineConfiguration.TimeoutAction;
import com.gh.bmd.jrt.channel.OutputChannel;
import com.gh.bmd.jrt.processor.annotation.Wrap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
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.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;
/**
* Annotation processor used to generate wrapper classes enabling method asynchronous invocations.
*
* Created by davide on 11/3/14.
*/
public class RoutineProcessor extends AbstractProcessor {
private static final boolean DEBUG = false;
private static final String NEW_LINE = System.getProperty("line.separator");
private boolean mDisabled;
private String mFooter;
private String mHeader;
private TypeElement mIterableElement;
private TypeElement mListElement;
private String mMethodArray;
private String mMethodArrayInvocation;
private String mMethodArrayInvocationCollection;
private String mMethodArrayInvocationVoid;
private String mMethodAsync;
private String mMethodHeader;
private String mMethodInvocation;
private String mMethodInvocationCollection;
private String mMethodInvocationVoid;
private String mMethodList;
private String mMethodParallelArray;
private String mMethodParallelAsync;
private String mMethodParallelList;
private String mMethodParallelResult;
private String mMethodParallelVoid;
private String mMethodResult;
private String mMethodVoid;
private TypeMirror mObjectType;
private TypeElement mOutputChannelElement;
@Override
public Set getSupportedAnnotationTypes() {
return Collections.singleton(Wrap.class.getCanonicalName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
// Let's return the latest version
final SourceVersion[] values = SourceVersion.values();
return values[values.length - 1];
}
@Override
public synchronized void init(final ProcessingEnvironment processingEnv) {
super.init(processingEnv);
final byte[] buffer = new byte[2048];
try {
mHeader = parseTemplate("/templates/header.txt", buffer);
mMethodHeader = parseTemplate("/templates/method_header.txt", buffer);
mMethodArray = parseTemplate("/templates/method_array.txt", buffer);
mMethodAsync = parseTemplate("/templates/method_async.txt", buffer);
mMethodList = parseTemplate("/templates/method_list.txt", buffer);
mMethodResult = parseTemplate("/templates/method_result.txt", buffer);
mMethodVoid = parseTemplate("/templates/method_void.txt", buffer);
mMethodParallelArray = parseTemplate("/templates/method_parallel_array.txt", buffer);
mMethodParallelAsync = parseTemplate("/templates/method_parallel_async.txt", buffer);
mMethodParallelList = parseTemplate("/templates/method_parallel_list.txt", buffer);
mMethodParallelResult = parseTemplate("/templates/method_parallel_result.txt", buffer);
mMethodParallelVoid = parseTemplate("/templates/method_parallel_void.txt", buffer);
mMethodInvocation = parseTemplate("/templates/method_invocation.txt", buffer);
mMethodInvocationCollection =
parseTemplate("/templates/method_invocation_collection.txt", buffer);
mMethodInvocationVoid = parseTemplate("/templates/method_invocation_void.txt", buffer);
mMethodArrayInvocation =
parseTemplate("/templates/method_array_invocation.txt", buffer);
mMethodArrayInvocationCollection =
parseTemplate("/templates/method_array_invocation_collection.txt", buffer);
mMethodArrayInvocationVoid =
parseTemplate("/templates/method_array_invocation_void.txt", buffer);
mFooter = parseTemplate("/templates/footer.txt", buffer);
mOutputChannelElement = getTypeFromName(OutputChannel.class.getCanonicalName());
mIterableElement = getTypeFromName(Iterable.class.getCanonicalName());
mListElement = getTypeFromName(List.class.getCanonicalName());
mObjectType = getTypeFromName(Object.class.getCanonicalName()).asType();
} catch (final IOException ex) {
mDisabled = true;
}
}
@Override
public boolean process(final Set extends TypeElement> typeElements,
final RoundEnvironment roundEnvironment) {
if (roundEnvironment.processingOver() || mDisabled) {
return false;
}
final TypeElement annotationElement = getTypeFromName(Wrap.class.getCanonicalName());
final TypeMirror annotationType = annotationElement.asType();
for (final Element element : ElementFilter.typesIn(
roundEnvironment.getElementsAnnotatedWith(Wrap.class))) {
final TypeElement classElement = (TypeElement) element;
final List methodElements =
ElementFilter.methodsIn(element.getEnclosedElements());
for (final TypeMirror typeMirror : classElement.getInterfaces()) {
final Element superElement = processingEnv.getTypeUtils().asElement(typeMirror);
if (superElement != null) {
mergeParentMethods(methodElements,
ElementFilter.methodsIn(superElement.getEnclosedElements()));
}
}
final Object targetElement = getElementValue(element, annotationType, "value");
if (targetElement != null) {
createWrapper(classElement, getTypeFromName(targetElement.toString()),
methodElements);
}
}
return false;
}
@Nonnull
private String buildCollectionParamValues(
@Nonnull final ExecutableElement targetMethodElement) {
final VariableElement targetParameter = targetMethodElement.getParameters().get(0);
return "(" + targetParameter.asType() + ") objects";
}
@Nonnull
private String buildGenericTypes(@Nonnull final TypeElement element) {
final List extends TypeParameterElement> typeParameters = element.getTypeParameters();
if (typeParameters.isEmpty()) {
return "";
}
final StringBuilder builder = new StringBuilder("<");
for (final TypeParameterElement typeParameterElement : typeParameters) {
if (builder.length() > 1) {
builder.append(", ");
}
builder.append(typeParameterElement.asType());
}
return builder.append(">").toString();
}
@Nonnull
private String buildInputParams(@Nonnull final ExecutableElement methodElement) {
final Types typeUtils = processingEnv.getTypeUtils();
final TypeElement outputChannelElement = mOutputChannelElement;
final StringBuilder builder = new StringBuilder();
for (final VariableElement variableElement : methodElement.getParameters()) {
builder.append(".pass(");
if (typeUtils.isAssignable(outputChannelElement.asType(),
typeUtils.erasure(variableElement.asType())) && (
variableElement.getAnnotation(Pass.class) != null)) {
builder.append("(com.gh.bmd.jrt.channel.OutputChannel)");
} else {
builder.append("(Object)");
}
builder.append(variableElement).append(")");
}
return builder.toString();
}
private String buildOutputOptions(final ExecutableElement methodElement) {
final StringBuilder builder = new StringBuilder();
final Timeout methodAnnotation = methodElement.getAnnotation(Timeout.class);
if (methodAnnotation != null) {
builder.append(".afterMax(")
.append(methodAnnotation.value())
.append(", ")
.append(TimeUnit.class.getCanonicalName())
.append(".")
.append(methodAnnotation.unit())
.append(")");
final TimeoutAction timeoutAction = methodAnnotation.action();
if (timeoutAction == TimeoutAction.DEADLOCK) {
builder.append(".eventuallyDeadlock()");
} else if (timeoutAction == TimeoutAction.EXIT) {
builder.append(".eventuallyExit()");
} else if (timeoutAction == TimeoutAction.ABORT) {
builder.append(".eventuallyAbort()");
}
}
return builder.toString();
}
@Nonnull
private String buildParamTypes(@Nonnull final ExecutableElement methodElement) {
final StringBuilder builder = new StringBuilder();
for (final VariableElement variableElement : methodElement.getParameters()) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append("final ")
.append(variableElement.asType())
.append(" ")
.append(variableElement);
}
return builder.toString();
}
@Nonnull
private String buildParamValues(@Nonnull final ExecutableElement targetMethodElement) {
int count = 0;
final StringBuilder builder = new StringBuilder();
for (final VariableElement variableElement : targetMethodElement.getParameters()) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append("(")
.append(getBoxedType(variableElement.asType()))
.append(") objects.get(")
.append(count++)
.append(")");
}
return builder.toString();
}
@Nonnull
private CharSequence buildParams(@Nonnull final ExecutableElement methodElement) {
final StringBuilder builder = new StringBuilder();
for (final VariableElement variableElement : methodElement.getParameters()) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(variableElement);
}
return builder.toString();
}
@Nonnull
private String buildRoutineFieldsInit(final int size) {
final StringBuilder builder = new StringBuilder();
for (int i = 1; i <= size; i++) {
builder.append("mRoutine")
.append(i)
.append(" = ")
.append("initRoutine")
.append(i)
.append("(configuration);")
.append(NEW_LINE);
}
return builder.toString();
}
@Nonnull
private String buildRoutineOptions(@Nonnull final ExecutableElement methodElement) {
final StringBuilder builder = new StringBuilder();
boolean isOverrideParameters = false;
for (final VariableElement parameterElement : methodElement.getParameters()) {
if (parameterElement.getAnnotation(Pass.class) != null) {
isOverrideParameters = true;
break;
}
}
builder.append(".withInputOrder(")
.append(OrderType.class.getCanonicalName())
.append(".")
.append((isOverrideParameters) ? OrderType.PASSING : OrderType.DELIVERY)
.append(")");
return builder.toString();
}
private String buildSizedArray(final TypeMirror returnType) {
final StringBuilder builder = new StringBuilder();
if (returnType.getKind() == TypeKind.ARRAY) {
final String typeString = returnType.toString();
final int firstBracket = typeString.indexOf('[');
builder.append(typeString.substring(0, firstBracket))
.append("[$$size]")
.append(typeString.substring(firstBracket));
} else {
builder.append(returnType).append("[$$size]");
}
return builder.toString();
}
@SuppressWarnings("PointlessBooleanExpression")
private void createWrapper(@Nonnull final TypeElement element,
@Nonnull final TypeElement targetElement,
@Nonnull final List methodElements) {
Writer writer = null;
try {
final String packageName = getPackage(element).getQualifiedName().toString();
final String interfaceName = element.getSimpleName().toString();
final Filer filer = processingEnv.getFiler();
if (!DEBUG) {
final JavaFileObject sourceFile =
filer.createSourceFile(packageName + ".JRoutine_" + interfaceName);
writer = sourceFile.openWriter();
} else {
writer = new StringWriter();
}
String header;
header = mHeader.replace("${packageName}", packageName);
header = header.replace("${genericTypes}", buildGenericTypes(element));
header = header.replace("${classFullName}", targetElement.asType().toString());
header = header.replace("${interfaceName}", interfaceName);
header = header.replace("${interfaceFullName}", element.asType().toString());
header = header.replace("${routineFieldsInit}",
buildRoutineFieldsInit(methodElements.size()));
writer.append(header);
int count = 0;
for (final ExecutableElement methodElement : methodElements) {
++count;
writeMethod(writer, targetElement, methodElement, count);
}
writer.append(mFooter);
} catch (final IOException ignored) {
} finally {
if (writer != null) {
try {
writer.close();
} catch (final IOException ignored) {
}
}
}
if (DEBUG) {
System.out.println(writer.toString());
}
}
@Nonnull
private ExecutableElement findMatchingMethod(@Nonnull final ExecutableElement methodElement,
@Nonnull final TypeElement targetElement) {
String methodName = methodElement.getSimpleName().toString();
ExecutableElement targetMethod = null;
final Bind asyncAnnotation = methodElement.getAnnotation(Bind.class);
if (asyncAnnotation != null) {
methodName = asyncAnnotation.value();
for (final ExecutableElement targetMethodElement : ElementFilter.methodsIn(
targetElement.getEnclosedElements())) {
final Bind targetAsyncAnnotation = targetMethodElement.getAnnotation(Bind.class);
if ((targetAsyncAnnotation != null) && methodName.equals(
targetAsyncAnnotation.value())) {
targetMethod = targetMethodElement;
break;
}
}
}
if (targetMethod == null) {
final Types typeUtils = processingEnv.getTypeUtils();
final TypeElement asyncAnnotationElement =
getTypeFromName(Pass.class.getCanonicalName());
final TypeMirror asyncAnnotationType = asyncAnnotationElement.asType();
final List extends VariableElement> interfaceTypeParameters =
methodElement.getParameters();
final int length = interfaceTypeParameters.size();
for (final ExecutableElement targetMethodElement : ElementFilter.methodsIn(
targetElement.getEnclosedElements())) {
if (!methodName.equals(targetMethodElement.getSimpleName().toString())) {
continue;
}
final List extends VariableElement> classTypeParameters =
targetMethodElement.getParameters();
if (length == classTypeParameters.size()) {
boolean matches = true;
for (int i = 0; i < length; i++) {
Object value = null;
final VariableElement variableElement = interfaceTypeParameters.get(i);
if (variableElement.getAnnotation(Pass.class) != null) {
value = getElementValue(variableElement, asyncAnnotationType, "value");
}
if (value != null) {
if (!typeUtils.isSameType((TypeMirror) value, typeUtils.erasure(
classTypeParameters.get(i).asType()))) {
matches = false;
break;
}
} else {
if (!typeUtils.isSameType(variableElement.asType(), typeUtils.erasure(
classTypeParameters.get(i).asType()))) {
matches = false;
break;
}
}
}
if (matches) {
targetMethod = targetMethodElement;
break;
}
}
}
}
if (targetMethod == null) {
throw new IllegalArgumentException(
"[" + methodElement + "] cannot find matching method in target class");
}
return targetMethod;
}
private TypeMirror getBoxedType(final TypeMirror type) {
if ((type != null) && (type.getKind().isPrimitive() || (type.getKind() == TypeKind.VOID))) {
return processingEnv.getTypeUtils().boxedClass((PrimitiveType) type).asType();
}
return type;
}
@Nullable
private Object getElementValue(@Nonnull final Element element,
@Nonnull final TypeMirror annotationType, @Nonnull final String valueName) {
AnnotationValue value = null;
for (final AnnotationMirror mirror : element.getAnnotationMirrors()) {
if (!mirror.getAnnotationType().equals(annotationType)) {
continue;
}
final Set extends Entry extends ExecutableElement, ? extends AnnotationValue>> set =
mirror.getElementValues().entrySet();
for (final Entry extends ExecutableElement, ? extends AnnotationValue> entry : set) {
if (valueName.equals(entry.getKey().getSimpleName().toString())) {
value = entry.getValue();
break;
}
}
}
return (value != null) ? value.getValue() : null;
}
@Nonnull
private PackageElement getPackage(@Nonnull final TypeElement element) {
return processingEnv.getElementUtils().getPackageOf(element);
}
@Nonnull
private PassingMode getParamMode(@Nonnull final ExecutableElement methodElement,
@Nonnull final Pass passAnnotation, @Nonnull final VariableElement targetParameter,
final int length) {
final Types typeUtils = processingEnv.getTypeUtils();
final TypeElement outputChannelElement = mOutputChannelElement;
final TypeElement iterableElement = mIterableElement;
final TypeElement listElement = mListElement;
final TypeMirror targetType = targetParameter.asType();
final TypeMirror targetTypeErasure = typeUtils.erasure(targetType);
final TypeElement annotationElement = getTypeFromName(Pass.class.getCanonicalName());
final TypeMirror annotationType = annotationElement.asType();
final TypeMirror targetMirror =
(TypeMirror) getElementValue(targetParameter, annotationType, "value");
PassingMode passingMode = passAnnotation.mode();
if (passingMode == PassingMode.AUTO) {
if (typeUtils.isAssignable(targetTypeErasure, outputChannelElement.asType())) {
if ((length == 1) && (targetMirror != null) && (
(targetMirror.getKind() == TypeKind.ARRAY) || typeUtils.isAssignable(
listElement.asType(), targetMirror))) {
passingMode = PassingMode.COLLECTION;
} else {
passingMode = PassingMode.OBJECT;
}
} else if ((targetType.getKind() == TypeKind.ARRAY) || typeUtils.isAssignable(
targetTypeErasure, iterableElement.asType())) {
if ((targetType.getKind() == TypeKind.ARRAY) && !typeUtils.isAssignable(
getBoxedType(((ArrayType) targetType).getComponentType()),
getBoxedType(targetMirror))) {
throw new IllegalArgumentException(
"[" + methodElement + "] the async input array with passing mode "
+ PassingMode.PARALLEL + " does not match the bound type: "
+ targetMirror);
}
if (length > 1) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async input with passing mode "
+ PassingMode.PARALLEL +
" cannot be applied to a method taking " + length +
" input parameters");
}
passingMode = PassingMode.PARALLEL;
} else {
throw new IllegalArgumentException(
"[" + methodElement + "] cannot automatically choose a "
+ "passing mode for an output of type: " + targetParameter);
}
} else if (passingMode == PassingMode.OBJECT) {
if (!typeUtils.isAssignable(targetTypeErasure, outputChannelElement.asType())) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async input with passing mode "
+ PassingMode.OBJECT + " must implement an "
+ outputChannelElement);
}
} else if (passingMode == PassingMode.COLLECTION) {
if (!typeUtils.isAssignable(targetTypeErasure, outputChannelElement.asType())) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async input with passing mode "
+ PassingMode.COLLECTION + " must implement an "
+ outputChannelElement);
}
if ((targetMirror != null) && (targetMirror.getKind() != TypeKind.ARRAY)
&& !typeUtils.isAssignable(listElement.asType(), targetMirror)) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async input with passing mode "
+ PassingMode.COLLECTION
+ " must be bound to an array or a superclass of " + listElement);
}
if (length > 1) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async input with passing mode "
+ PassingMode.COLLECTION +
" cannot be applied to a method taking " + length
+ " input parameters");
}
} else { // PassingMode.PARALLEL
if ((targetType.getKind() != TypeKind.ARRAY) && !typeUtils.isAssignable(
targetTypeErasure, iterableElement.asType())) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async input with passing mode "
+ PassingMode.PARALLEL + " must be an array or implement an "
+ iterableElement);
}
if ((targetType.getKind() == TypeKind.ARRAY) && !typeUtils.isAssignable(
getBoxedType(((ArrayType) targetType).getComponentType()),
getBoxedType(targetMirror))) {
throw new IllegalArgumentException(
"[" + methodElement + "] the async input array with passing mode "
+ PassingMode.PARALLEL + " does not match the bound type: "
+ targetMirror);
}
if (length > 1) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async input with passing mode "
+ PassingMode.PARALLEL +
" cannot be applied to a method taking " + length
+ " input parameters");
}
}
return passingMode;
}
@Nonnull
private PassingMode getReturnMode(@Nonnull final Pass annotation,
@Nonnull final ExecutableElement methodElement) {
final Types typeUtils = processingEnv.getTypeUtils();
final TypeElement outputChannelElement = mOutputChannelElement;
final TypeElement iterableElement = mIterableElement;
final TypeElement listElement = mListElement;
final TypeMirror returnType = methodElement.getReturnType();
final TypeMirror returnTypeErasure = typeUtils.erasure(returnType);
final TypeElement annotationElement = getTypeFromName(Pass.class.getCanonicalName());
final TypeMirror annotationType = annotationElement.asType();
final TypeMirror targetMirror =
(TypeMirror) getElementValue(methodElement, annotationType, "value");
PassingMode passingMode = annotation.mode();
if (passingMode == PassingMode.AUTO) {
if ((returnType.getKind() == TypeKind.ARRAY) || typeUtils.isAssignable(
listElement.asType(), returnTypeErasure)) {
if ((returnType.getKind() == TypeKind.ARRAY) && !typeUtils.isAssignable(
getBoxedType(targetMirror),
getBoxedType(((ArrayType) returnType).getComponentType()))) {
throw new IllegalArgumentException(
"[" + methodElement + "] the async output array with passing mode "
+ PassingMode.PARALLEL + " does not match the bound type: "
+ targetMirror);
}
passingMode = PassingMode.PARALLEL;
} else if (typeUtils.isAssignable(outputChannelElement.asType(), returnTypeErasure)) {
if ((targetMirror != null) && ((targetMirror.getKind() == TypeKind.ARRAY)
|| typeUtils.isAssignable(targetMirror, iterableElement.asType()))) {
passingMode = PassingMode.COLLECTION;
} else {
passingMode = PassingMode.OBJECT;
}
} else {
throw new IllegalArgumentException(
"[" + methodElement + "] cannot automatically choose a "
+ "passing mode for an input of type: " + returnType);
}
} else if (passingMode == PassingMode.OBJECT) {
if (!typeUtils.isAssignable(outputChannelElement.asType(), returnTypeErasure)) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async output with passing mode "
+ PassingMode.OBJECT + " must be a superclass of "
+ outputChannelElement);
}
} else if (passingMode == PassingMode.COLLECTION) {
if (!typeUtils.isAssignable(outputChannelElement.asType(), returnTypeErasure)) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async output with passing mode "
+ PassingMode.OBJECT + " must be a superclass of "
+ outputChannelElement);
}
if ((targetMirror != null) && (targetMirror.getKind() != TypeKind.ARRAY)
&& !typeUtils.isAssignable(targetMirror, iterableElement.asType())) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async output with passing mode "
+ PassingMode.COLLECTION
+ " must be bound to an array or a type implementing an "
+ iterableElement);
}
} else { // PassingMode.PARALLEL
if ((returnType.getKind() != TypeKind.ARRAY) && !typeUtils.isAssignable(
listElement.asType(), returnTypeErasure)) {
throw new IllegalArgumentException(
"[" + methodElement + "] an async output with passing mode "
+ PassingMode.PARALLEL + " must be an array or a superclass of "
+ listElement);
}
if ((returnType.getKind() == TypeKind.ARRAY) && !typeUtils.isAssignable(
getBoxedType(targetMirror),
getBoxedType(((ArrayType) returnType).getComponentType()))) {
throw new IllegalArgumentException(
"[" + methodElement + "] the async output array with passing mode "
+ PassingMode.PARALLEL + " does not match the bound type: "
+ targetMirror);
}
}
return passingMode;
}
@Nonnull
private TypeElement getTypeFromName(@Nonnull final String typeName) {
return processingEnv.getElementUtils().getTypeElement(normalizeTypeName(typeName));
}
private boolean haveSameParameters(@Nonnull final ExecutableElement firstMethodElement,
@Nonnull final ExecutableElement secondMethodElement) {
final List extends VariableElement> firstTypeParameters =
firstMethodElement.getParameters();
final List extends VariableElement> secondTypeParameters =
secondMethodElement.getParameters();
final int length = firstTypeParameters.size();
if (length != secondTypeParameters.size()) {
return false;
}
final Types typeUtils = processingEnv.getTypeUtils();
for (int i = 0; i < length; i++) {
final TypeMirror firstType = firstTypeParameters.get(i).asType();
final TypeMirror secondType = secondTypeParameters.get(i).asType();
if (!typeUtils.isSameType(firstType, secondType)) {
return false;
}
}
return true;
}
private void mergeParentMethods(@Nonnull final List methods,
@Nonnull final List parentMethods) {
for (final ExecutableElement parentMethod : parentMethods) {
boolean isOverride = false;
for (final ExecutableElement method : methods) {
if (parentMethod.getSimpleName().equals(method.getSimpleName())
&& parentMethod.getReturnType().equals(method.getReturnType())) {
if (haveSameParameters(parentMethod, method)) {
isOverride = true;
break;
}
}
}
if (!isOverride) {
methods.add(parentMethod);
}
}
}
@Nonnull
private String normalizeTypeName(@Nonnull final String typeName) {
if (typeName.endsWith(".class")) {
return typeName.substring(0, typeName.length() - ".class".length());
}
return typeName;
}
@Nonnull
private String parseTemplate(@Nonnull final String path, @Nonnull final byte[] buffer) throws
IOException {
final InputStream resourceStream = RoutineProcessor.class.getResourceAsStream(path);
try {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
int length;
while ((length = resourceStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
outputStream.flush();
return outputStream.toString("UTF-8");
} catch (final IOException ex) {
processingEnv.getMessager()
.printMessage(Kind.ERROR,
"IOException while reading " + path + " template");
throw ex;
} finally {
try {
resourceStream.close();
} catch (final IOException ignored) {
}
}
}
private void writeMethod(@Nonnull final Writer writer, @Nonnull final TypeElement targetElement,
@Nonnull final ExecutableElement methodElement, final int count) throws IOException {
final Types typeUtils = processingEnv.getTypeUtils();
final TypeElement outputChannelElement = mOutputChannelElement;
final TypeElement listElement = mListElement;
final TypeMirror objectType = mObjectType;
final ExecutableElement targetMethod = findMatchingMethod(methodElement, targetElement);
TypeMirror targetReturnType = targetMethod.getReturnType();
final boolean isVoid = (targetReturnType.getKind() == TypeKind.VOID);
final Pass methodAnnotation = methodElement.getAnnotation(Pass.class);
PassingMode asyncParamMode = null;
PassingMode asyncReturnMode = null;
final List extends VariableElement> parameters = methodElement.getParameters();
for (final VariableElement parameter : parameters) {
final Pass annotation = parameter.getAnnotation(Pass.class);
if (annotation == null) {
continue;
}
asyncParamMode = getParamMode(methodElement, annotation, parameter, parameters.size());
}
String method;
if (methodAnnotation != null) {
asyncReturnMode = getReturnMode(methodAnnotation, methodElement);
final TypeMirror returnType = methodElement.getReturnType();
final TypeMirror returnTypeErasure = typeUtils.erasure(returnType);
if (returnType.getKind() == TypeKind.ARRAY) {
targetReturnType = ((ArrayType) returnType).getComponentType();
method = ((asyncParamMode == PassingMode.PARALLEL) && !typeUtils.isAssignable(
methodElement.getParameters().get(0).asType(),
outputChannelElement.asType())) ? mMethodParallelArray : mMethodArray;
} else if (typeUtils.isAssignable(listElement.asType(), returnTypeErasure)) {
final List extends TypeMirror> typeArguments =
((DeclaredType) returnType).getTypeArguments();
if (typeArguments.isEmpty()) {
targetReturnType = objectType;
} else {
targetReturnType = typeArguments.get(0);
}
method = ((asyncParamMode == PassingMode.PARALLEL) && !typeUtils.isAssignable(
methodElement.getParameters().get(0).asType(),
outputChannelElement.asType())) ? mMethodParallelList : mMethodList;
} else if (typeUtils.isAssignable(outputChannelElement.asType(), returnTypeErasure)) {
final List extends TypeMirror> typeArguments =
((DeclaredType) returnType).getTypeArguments();
if (typeArguments.isEmpty()) {
targetReturnType = objectType;
} else {
targetReturnType = typeArguments.get(0);
}
method = ((asyncParamMode == PassingMode.PARALLEL) && !typeUtils.isAssignable(
methodElement.getParameters().get(0).asType(),
outputChannelElement.asType())) ? mMethodParallelAsync : mMethodAsync;
} else {
throw new IllegalArgumentException("[" + methodElement + "] invalid return type");
}
} else if (isVoid) {
method = ((asyncParamMode == PassingMode.PARALLEL) && !typeUtils.isAssignable(
methodElement.getParameters().get(0).asType(), outputChannelElement.asType()))
? mMethodParallelVoid : mMethodVoid;
} else {
targetReturnType = methodElement.getReturnType();
method = ((asyncParamMode == PassingMode.PARALLEL) && !typeUtils.isAssignable(
methodElement.getParameters().get(0).asType(), outputChannelElement.asType()))
? mMethodParallelResult : mMethodResult;
}
final String resultClassName = getBoxedType(targetReturnType).toString();
String methodHeader;
methodHeader = mMethodHeader.replace("${resultClassName}", resultClassName);
methodHeader = methodHeader.replace("${methodCount}", Integer.toString(count));
methodHeader = methodHeader.replace("${routineBuilderOptions}",
buildRoutineOptions(methodElement));
writer.append(methodHeader);
method = method.replace("${resultClassName}", resultClassName);
method = method.replace("${resultRawClass}", targetReturnType.toString());
method = method.replace("${resultRawSizedArray}", buildSizedArray(targetReturnType));
method = method.replace("${resultType}", methodElement.getReturnType().toString());
method = method.replace("${methodCount}", Integer.toString(count));
method = method.replace("${methodName}", methodElement.getSimpleName());
method = method.replace("${params}", buildParams(methodElement));
method = method.replace("${paramTypes}", buildParamTypes(methodElement));
method = method.replace("${inputParams}", buildInputParams(methodElement));
method = method.replace("${outputOptions}", buildOutputOptions(methodElement));
method = method.replace("${invokeMethod}",
(asyncParamMode == PassingMode.PARALLEL) ? "invokeParallel"
: "invokeAsync");
writer.append(method);
String methodInvocation;
if ((asyncParamMode == PassingMode.COLLECTION) && (
targetMethod.getParameters().get(0).asType().getKind() == TypeKind.ARRAY)) {
final ArrayType arrayType = (ArrayType) targetMethod.getParameters().get(0).asType();
methodInvocation = (isVoid) ? mMethodArrayInvocationVoid
: (asyncReturnMode == PassingMode.COLLECTION) ? mMethodArrayInvocationCollection
: mMethodArrayInvocation;
methodInvocation = methodInvocation.replace("${componentType}",
arrayType.getComponentType().toString());
methodInvocation = methodInvocation.replace("${boxedType}", getBoxedType(
arrayType.getComponentType()).toString());
} else {
methodInvocation = (isVoid) ? mMethodInvocationVoid
: (asyncReturnMode == PassingMode.COLLECTION) ? mMethodInvocationCollection
: mMethodInvocation;
}
methodInvocation =
methodInvocation.replace("${classFullName}", targetElement.asType().toString());
methodInvocation = methodInvocation.replace("${resultClassName}", resultClassName);
methodInvocation = methodInvocation.replace("${methodCount}", Integer.toString(count));
methodInvocation = methodInvocation.replace("${methodName}", methodElement.getSimpleName());
methodInvocation =
methodInvocation.replace("${targetMethodName}", targetMethod.getSimpleName());
if (asyncParamMode == PassingMode.COLLECTION) {
methodInvocation = methodInvocation.replace("${maxParamSize}",
Integer.toString(Integer.MAX_VALUE));
methodInvocation = methodInvocation.replace("${paramValues}",
buildCollectionParamValues(targetMethod));
} else {
methodInvocation = methodInvocation.replace("${maxParamSize}", Integer.toString(
targetMethod.getParameters().size()));
methodInvocation =
methodInvocation.replace("${paramValues}", buildParamValues(targetMethod));
}
final Share shareAnnotation = methodElement.getAnnotation(Share.class);
if (shareAnnotation != null) {
methodInvocation = methodInvocation.replace("${shareGroup}",
"\"" + shareAnnotation.value() + "\"");
} else {
methodInvocation = methodInvocation.replace("${shareGroup}", "\"null\"");
}
writer.append(methodInvocation);
}
}