Skip to content

Instantly share code, notes, and snippets.

@tbroyer
Created June 4, 2017 09:44
Show Gist options
  • Save tbroyer/420004d02e905ba7ce9489d24d3bae72 to your computer and use it in GitHub Desktop.
Save tbroyer/420004d02e905ba7ce9489d24d3bae72 to your computer and use it in GitHub Desktop.
Proof-of-concept annotation processor for gwteventbinder
/*
* Copyright 2013 Google Inc.
* Copyright 2017 Thomas Broyer
*
* 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.google.web.bindery.event.processor.binder;
import com.google.auto.common.AnnotationMirrors;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.service.AutoService;
import com.google.common.base.Equivalence;
import com.google.common.base.Optional;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.event.shared.HandlerRegistration;
import com.google.web.bindery.event.shared.binder.EventHandler;
import com.google.web.bindery.event.shared.binder.GenericEvent;
import com.google.web.bindery.event.shared.binder.impl.AbstractEventBinder;
import com.google.web.bindery.event.shared.binder.impl.GenericEventHandler;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.annotation.Generated;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
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.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.util.TypeKindVisitor7;
import javax.tools.Diagnostic;
@AutoService(Processor.class)
public class EventHandlerProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(EventHandler.class.getCanonicalName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
roundEnv
.getElementsAnnotatedWith(EventHandler.class)
.stream()
// EventHandler is only applicable to methods, let the compiler handle the bad usages
.filter(element -> element.getKind() == ElementKind.METHOD)
.map(element -> (TypeElement) element.getEnclosingElement())
.distinct()
.forEach(this::generate);
return false;
}
private void generate(TypeElement target) {
ClassName targetClassName = ClassName.get(target);
ClassName outputClassName =
ClassName.get(
targetClassName.packageName(),
"EventBinder_" + String.join("_", targetClassName.simpleNames()));
MethodSpec.Builder methodBuilder =
MethodSpec.methodBuilder("doBindEventHandlers")
.addAnnotation(Override.class)
.addModifiers(Modifier.PROTECTED)
.returns(ParameterizedTypeName.get(List.class, HandlerRegistration.class))
.addParameter(targetClassName, "target", Modifier.FINAL)
.addParameter(EventBus.class, "eventBus")
.addStatement(
"$T registrations = new $T()",
ParameterizedTypeName.get(List.class, HandlerRegistration.class),
ParameterizedTypeName.get(LinkedList.class, HandlerRegistration.class));
MoreElements.getLocalAndInheritedMethods(
target, processingEnv.getTypeUtils(), processingEnv.getElementUtils())
.stream()
.filter(executableElement -> !executableElement.getModifiers().contains(Modifier.PRIVATE))
.forEach(
executableElement -> {
Optional<AnnotationMirror> annotation =
MoreElements.getAnnotationMirror(executableElement, EventHandler.class);
if (annotation.isPresent()) {
generateHandlerForBindMethod(annotation.get(), methodBuilder, executableElement);
}
});
methodBuilder.addStatement("return registrations");
try {
JavaFile.builder(
outputClassName.packageName(),
TypeSpec.classBuilder(outputClassName)
.addAnnotation(
AnnotationSpec.builder(Generated.class)
.addMember("value", "$S", EventHandlerProcessor.class.getCanonicalName())
.build())
.superclass(
ParameterizedTypeName.get(
ClassName.get(AbstractEventBinder.class), targetClassName))
.addOriginatingElement(target)
.addMethod(methodBuilder.build())
.build())
.build()
.writeTo(processingEnv.getFiler());
} catch (IOException e) {
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
String.format("Error writing %s: %s", outputClassName, e),
target);
}
}
private void generateHandlerForBindMethod(
AnnotationMirror annotation, MethodSpec.Builder methodBuilder, ExecutableElement method) {
DeclaredType eventParameter = null;
if (method.getParameters().size() == 1) {
eventParameter =
method
.getParameters()
.get(0)
.asType()
.accept(
new TypeKindVisitor7<DeclaredType, Void>() {
@Override
public DeclaredType visitDeclared(DeclaredType t, Void v) {
switch (t.asElement().getKind()) {
case CLASS:
case INTERFACE:
return t;
default:
return null;
}
}
},
null);
}
@SuppressWarnings("unchecked")
List<? extends AnnotationValue> handles =
(List<? extends AnnotationValue>)
AnnotationMirrors.getAnnotationValue(annotation, "handles").getValue();
if (handles.size() == 0 && !isAConcreteGenericEvent(eventParameter)) {
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Method "
+ method.getSimpleName()
+ " annotated with @EventHandler without event classes must have exactly "
+ "one argument of a concrete type assignable to GenericEvent",
method);
return;
}
Set<Equivalence.Wrapper<DeclaredType>> eventTypes = new LinkedHashSet<>();
if (handles.size() != 0) {
for (AnnotationValue value : handles) {
DeclaredType event = (DeclaredType) value.getValue();
if (eventParameter != null
&& !processingEnv.getTypeUtils().isAssignable(event, eventParameter)) {
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Event "
+ ((TypeElement) event.asElement()).getQualifiedName()
+ " isn't assignable to "
+ ((TypeElement) eventParameter.asElement()).getQualifiedName()
+ " in method: "
+ method.getSimpleName(),
method,
annotation,
value);
}
eventTypes.add(MoreTypes.equivalence().wrap(event));
}
} else {
eventTypes.add(MoreTypes.equivalence().wrap(eventParameter));
}
for (Equivalence.Wrapper<DeclaredType> eventType : eventTypes) {
methodBuilder.addStatement(
"bind(eventBus, registrations, $T.class, $L)",
eventType.get(),
TypeSpec.anonymousClassBuilder(CodeBlock.of(""))
.addSuperinterface(GenericEventHandler.class)
.addMethod(
MethodSpec.methodBuilder("handleEvent")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(GenericEvent.class, "event")
.addStatement(
"target.$N($L)",
method.getSimpleName(),
eventParameter == null ? "" : CodeBlock.of("($T) event", eventType.get()))
.build())
.build());
}
}
private boolean isAConcreteGenericEvent(DeclaredType param) {
return param != null
&& !param.asElement().getModifiers().contains(Modifier.ABSTRACT)
&& processingEnv
.getTypeUtils()
.isAssignable(
param,
processingEnv
.getElementUtils()
.getTypeElement(GenericEvent.class.getCanonicalName())
.asType());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment