/* * Copyright 2014 Red Hat, Inc. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.codegen; import io.vertx.codegen.annotations.ProxyClose; import io.vertx.codegen.annotations.ProxyIgnore; import io.vertx.codegen.doc.Doc; import io.vertx.codegen.doc.Text; import io.vertx.codegen.overloadcheck.MethodOverloadChecker; import io.vertx.codegen.type.*; import javax.annotation.processing.Messager; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; 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.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; /** * @author <a href="http://tfox.org">Tim Fox</a> */ public class ProxyModel extends ClassModel { public ProxyModel(MethodOverloadChecker methodOverloadChecker, Messager messager, Map<String, TypeElement> sources, Elements elementUtils, Types typeUtils, TypeElement modelElt) { super(methodOverloadChecker, messager, sources, elementUtils, typeUtils, modelElt); } @Override public String getKind() { return "proxy"; } @Override protected void checkParamType(ExecutableElement elem, TypeMirror type, TypeInfo typeInfo, int pos, int numParams) { // Basic types, int, long, String etc // JsonObject or JsonArray if (typeInfo.getKind().basic || typeInfo.getKind().json) { return; } // We also allow enums as parameter types if (typeInfo.getKind() == ClassKind.ENUM) { return; } if (isLegalContainerParam(typeInfo)) { return; } // We also allow data object as parameter types if they have a 'public JsonObject toJson()' method if (typeInfo.getKind() == ClassKind.DATA_OBJECT) { if (type instanceof DeclaredType) { if (Helper.isJsonifiable(elementUtils, typeUtils, (TypeElement) ((DeclaredType) type).asElement())) { return; } throw new GenException(elem, "type " + typeInfo + " does not have a valid 'public JsonObject toJson()' method."); } } if (isLegalHandlerAsyncResultType(typeInfo)) { if (pos != numParams - 1) { throw new GenException(elem, "Handler<AsyncResult<T>> must be the last parameter if present in a proxied method"); } return; } if (elem.getModifiers().contains(Modifier.STATIC)) { // Ignore static methods - we won't use them anyway return; } throw new GenException(elem, "type " + typeInfo + " is not legal for use for a parameter in proxy"); } @Override protected void checkReturnType(ExecutableElement elem, TypeInfo type, TypeMirror typeMirror) { if (elem.getModifiers().contains(Modifier.STATIC)) { // Ignore static methods - we won't use them anyway return; } if (type instanceof VoidTypeInfo) { return; } throw new GenException(elem, "Proxy methods must have void or Fluent returns"); } @Override protected void checkMethod(MethodInfo methodInfo) { // We don't allow overloaded methods in proxies List<MethodInfo> methodsByName = methodMap.get(methodInfo.getName()); if (methodsByName != null) { throw new GenException(this.modelElt, "Overloaded methods are not allowed in ProxyGen interfaces " + methodInfo.name); } } @Override protected MethodInfo createMethodInfo(Set<ClassTypeInfo> ownerTypes, String methodName, String comment, Doc doc, MethodKind kind, TypeInfo returnType, Text returnDescription, boolean isFluent, boolean isCacheReturn, List<ParamInfo> mParams, ExecutableElement methodElt, boolean isStatic, boolean isDefault, ArrayList<TypeParamInfo.Method> typeParams, TypeElement declaringElt) { AnnotationMirror proxyIgnoreAnnotation = Helper.resolveMethodAnnotation(ProxyIgnore.class, elementUtils, typeUtils, declaringElt, methodElt); boolean isProxyIgnore = proxyIgnoreAnnotation != null; AnnotationMirror proxyCloseAnnotation = Helper.resolveMethodAnnotation(ProxyClose.class, elementUtils, typeUtils, declaringElt, methodElt); boolean isProxyClose = proxyCloseAnnotation != null; if (isProxyClose && mParams.size() > 0) { if (mParams.size() > 1) { throw new GenException(this.modelElt, "@ProxyClose methods can't have more than one parameter"); } if (kind != MethodKind.FUTURE) { throw new GenException(this.modelElt, "@ProxyClose parameter must be Handler<AsyncResult<Void>>"); } TypeInfo type = mParams.get(0).getType(); TypeInfo arg = ((ParameterizedTypeInfo) ((ParameterizedTypeInfo) type).getArgs().get(0)).getArgs().get(0); if (arg.getKind() != ClassKind.VOID) { throw new GenException(this.modelElt, "@ProxyClose parameter must be " + "Handler<AsyncResult<Void>> instead of " + type); } } return new ProxyMethodInfo(ownerTypes, methodName, kind, returnType, returnDescription, isFluent, isCacheReturn, mParams, comment, doc, isStatic, isDefault, typeParams, isProxyIgnore, isProxyClose); } private boolean isLegalHandlerAsyncResultType(TypeInfo type) { if (type.getErased().getKind() == ClassKind.HANDLER) { TypeInfo eventType = ((ParameterizedTypeInfo) type).getArgs().get(0); if (eventType.getErased().getKind() == ClassKind.ASYNC_RESULT) { TypeInfo resultType = ((ParameterizedTypeInfo) eventType).getArgs().get(0); if (resultType.getKind().json || resultType.getKind().basic || isLegalListSetMapResult(resultType) || resultType.getKind() == ClassKind.VOID || resultType.getKind() == ClassKind.ENUM || resultType.getKind() == ClassKind.DATA_OBJECT) { return true; } if (resultType.getKind() == ClassKind.API) { ApiTypeInfo cla = (ApiTypeInfo)resultType; if (cla.isProxyGen()) { return true; } } } } return false; } private boolean isLegalListSetMapResult(TypeInfo type) { if (type instanceof ParameterizedTypeInfo) { if (type.getKind() == ClassKind.LIST || type.getKind() == ClassKind.SET) { TypeInfo elementType = ((ParameterizedTypeInfo) type).getArgs().get(0); if (elementType.getKind().basic || elementType.getKind().json || elementType.getKind() == ClassKind.DATA_OBJECT) { return true; } } } return false; } // TODO should we allow enums/Data objects in List/Set/Map params protected boolean isLegalContainerParam(TypeInfo type) { TypeInfo raw = type.getRaw(); if (raw.getName().equals(List.class.getName()) || raw.getName().equals(Set.class.getName())) { TypeInfo argument = ((ParameterizedTypeInfo) type).getArgs().get(0); if (argument.getKind().basic || argument.getKind().json || argument.getKind() == ClassKind.DATA_OBJECT) { return true; } } else if (raw.getName().equals(Map.class.getName())) { TypeInfo argument0 = ((ParameterizedTypeInfo) type).getArgs().get(0); if (!argument0.getName().equals(String.class.getName())) { return false; } TypeInfo argument1 = ((ParameterizedTypeInfo) type).getArgs().get(1); if (argument1.getKind().basic || argument1.getKind().json) { return true; } } return false; } }