/* * Copyright 2016 ArcBees Inc. * * 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.gwtplatform.mvp.processors.proxy; import java.util.Collection; import java.util.List; 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.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import com.google.common.base.Optional; import com.google.common.collect.FluentIterable; import com.google.web.bindery.event.shared.Event; import com.gwtplatform.processors.tools.domain.HasImports; import com.gwtplatform.processors.tools.domain.Type; import com.gwtplatform.processors.tools.exceptions.UnableToProcessException; import com.gwtplatform.processors.tools.logger.Logger; import com.gwtplatform.processors.tools.utils.Utils; import static javax.lang.model.util.ElementFilter.fieldsIn; import static javax.lang.model.util.ElementFilter.methodsIn; import static com.google.auto.common.MoreElements.hasModifiers; import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.auto.common.MoreTypes.asExecutable; import static com.google.auto.common.MoreTypes.asMemberOf; import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.auto.common.MoreTypes.isType; import static com.google.auto.common.MoreTypes.isTypeOf; import static com.google.auto.common.MoreTypes.nonObjectSuperclass; import static com.google.common.collect.FluentIterable.from; public class ProxyEventMethod implements HasImports { private final Logger logger; private final Utils utils; private final ExecutableElement element; private DeclaredType eventTypeMirror; private DeclaredType handlerTypeMirror; private String annotatedMethodName; private String handlerMethodName; private String typeAccessor; public ProxyEventMethod( Logger logger, Utils utils, ExecutableElement element) { this.logger = logger; this.utils = utils; this.element = element; } public String getHandlerMethodName() { if (handlerMethodName == null) { ExecutableElement handlerMethod = extractOnlyHandlerMethod(); validateEventHandler(handlerMethod); handlerMethodName = handlerMethod.getSimpleName().toString(); } return handlerMethodName; } private ExecutableElement extractOnlyHandlerMethod() { TypeElement handlerElement = asTypeElement(getHandlerTypeMirror()); List<ExecutableElement> handlerMethods = methodsIn(handlerElement.getEnclosedElements()); if (handlerMethods.size() != 1) { throwBadHandlerSignature(handlerElement); } return handlerMethods.get(0); } private void validateEventHandler(ExecutableElement handlerMethod) { if (handlerMethod.getReturnType().getKind() != TypeKind.VOID) { throwBadHandlerSignature(handlerMethod); } List<? extends VariableElement> parameters = handlerMethod.getParameters(); if (parameters.size() != 1) { throwBadHandlerSignature(handlerMethod); } VariableElement parameter = parameters.get(0); TypeMirror parameterType = asMemberOf(utils.getTypes(), getHandlerTypeMirror(), parameter); if (!utils.getTypes().isSameType(parameterType, getEventTypeMirror())) { throwBadHandlerSignature(parameter); } } private void throwBadHandlerSignature(Element element) { logger.error() .context(element) .log("Event handler used in a @ProxyEvent context must contain a single method returning void and " + "accepting a single argument of type `%s`.", getEventType()); throw new UnableToProcessException(); } public Type getHandlerType() { return new Type(getHandlerTypeMirror()); } private DeclaredType getHandlerTypeMirror() { if (handlerTypeMirror == null) { Optional<DeclaredType> optionalSuperType = nonObjectSuperclass(utils.getTypes(), utils.getElements(), getEventTypeMirror()); while (optionalSuperType.isPresent()) { DeclaredType superType = optionalSuperType.get(); if (isTypeOf(Event.class, superType)) { handlerTypeMirror = extractHandlerTypeFromEvent(superType); break; } else { optionalSuperType = nonObjectSuperclass(utils.getTypes(), utils.getElements(), superType); } } } return handlerTypeMirror; } private DeclaredType extractHandlerTypeFromEvent(DeclaredType superType) { List<? extends TypeMirror> typeArguments = superType.getTypeArguments(); if (typeArguments.size() != 1) { logger.error().context(eventTypeMirror.asElement()) .log("GWT event does not specify a proper handler type while extending Event."); throw new UnableToProcessException(); } DeclaredType typeMirror = asDeclared(typeArguments.get(0)); if (typeMirror.asElement().getKind() != ElementKind.INTERFACE) { logger.error().context(typeMirror.asElement()).log("GWT event handler must be an interface."); throw new UnableToProcessException(); } return typeMirror; } public String getTypeAccessor() { if (typeAccessor == null) { TypeElement eventTypeElement = asTypeElement(getEventTypeMirror()); List<? extends Element> members = utils.getElements().getAllMembers(eventTypeElement); Optional<ExecutableElement> methodAccessor = extractEventTypeMethodAccessor(members); Optional<VariableElement> fieldAccessor = extractEventTypeFieldAccessor(members); // TODO: We could explicitly check that in Event<H1> and Type<H2>, H1 === H2. // For now, we will get compile time error. if (methodAccessor.isPresent()) { typeAccessor = methodAccessor.get().getSimpleName().toString() + "()"; } else if (fieldAccessor.isPresent()) { typeAccessor = fieldAccessor.get().getSimpleName().toString(); } else { logger.error().context(eventTypeElement) .log("Event used with a @ProxyEvent, but it does not contain a static field `TYPE` or a static " + "method `getType()` returning a valid `Event.Type<>` instance."); throw new UnableToProcessException(); } } return typeAccessor; } private Optional<ExecutableElement> extractEventTypeMethodAccessor(List<? extends Element> members) { return FluentIterable.from(methodsIn(members)) .firstMatch(method -> method.getSimpleName().contentEquals("getType") && method.getParameters().isEmpty() && isGwtEventType(method, method.getReturnType())); } private Optional<VariableElement> extractEventTypeFieldAccessor(List<? extends Element> members) { return FluentIterable.from(fieldsIn(members)) .firstMatch(variable -> variable.getSimpleName().contentEquals("TYPE") && isGwtEventType(variable, variable.asType())); } private boolean isGwtEventType(Element element, TypeMirror typeMirror) { return hasModifiers(Modifier.PUBLIC, Modifier.STATIC).apply(element) && isType(typeMirror) && utils.getTypes().isSubtype(typeMirror, utils.createWithWildcard(Event.Type.class)) && asDeclared(typeMirror).getTypeArguments().size() == 1; } public Type getEventType() { return new Type(getEventTypeMirror()); } private DeclaredType getEventTypeMirror() { if (eventTypeMirror == null) { List<? extends TypeMirror> parameterTypes = asExecutable(element.asType()).getParameterTypes(); if (parameterTypes.size() != 1) { throwInvalidProxyMethodArguments(); } TypeMirror parameterType = parameterTypes.get(0); TypeMirror genericEventType = utils.createWithWildcard(Event.class); if (!utils.getTypes().isAssignable(parameterType, genericEventType)) { throwInvalidProxyMethodArguments(); } eventTypeMirror = asDeclared(parameterType); } return eventTypeMirror; } private DeclaredType throwInvalidProxyMethodArguments() { logger.error().context(element) .log("@ProxyEvent method must accept a single argument extending GWT's Event."); throw new UnableToProcessException(); } public String getAnnotatedMethodName() { if (annotatedMethodName == null) { if (!element.getThrownTypes().isEmpty()) { logger.error().context(element).log("@ProxyEvent method cannot throw exceptions."); throw new UnableToProcessException(); } else if (!element.getTypeParameters().isEmpty()) { logger.error().context(element).log("@ProxyEvent method cannot have type parameters."); throw new UnableToProcessException(); } else if (hasModifiers(Modifier.PRIVATE).apply(element)) { logger.error().context(element) .log("@ProxyEvent method must not be private. Package-local is recommended."); throw new UnableToProcessException(); } else if (element.getReturnType().getKind() != TypeKind.VOID) { logger.warning().context(element) .log("@ProxyEvent method does not return void. The returned value will be ignored."); } annotatedMethodName = element.getSimpleName().toString(); } return annotatedMethodName; } @Override public Collection<String> getImports() { return from(getEventType().getImports()) .append(getHandlerType().getImports()) .toList(); } }