diff --git a/src/main/java/io/vertx/codegen/ClassModel.java b/src/main/java/io/vertx/codegen/ClassModel.java index 39a92acc7..fe0cd1e48 100644 --- a/src/main/java/io/vertx/codegen/ClassModel.java +++ b/src/main/java/io/vertx/codegen/ClassModel.java @@ -16,26 +16,51 @@ * You may elect to redistribute this code under either of these licenses. */ -import io.vertx.codegen.annotations.CacheReturn; -import io.vertx.codegen.annotations.Fluent; import io.vertx.codegen.annotations.GenIgnore; import io.vertx.codegen.annotations.VertxGen; import io.vertx.codegen.doc.Doc; -import io.vertx.codegen.doc.Tag; import io.vertx.codegen.doc.Text; import io.vertx.codegen.doc.Token; import io.vertx.codegen.overloadcheck.MethodOverloadChecker; -import io.vertx.codegen.type.*; +import io.vertx.codegen.type.AnnotationValueInfo; +import io.vertx.codegen.type.AnnotationValueInfoFactory; +import io.vertx.codegen.type.ApiTypeInfo; +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.ClassTypeInfo; +import io.vertx.codegen.type.DataObjectTypeInfo; +import io.vertx.codegen.type.EnumTypeInfo; +import io.vertx.codegen.type.ParameterizedTypeInfo; +import io.vertx.codegen.type.TypeInfo; +import io.vertx.codegen.type.TypeMirrorFactory; +import io.vertx.codegen.type.TypeVariableInfo; +import io.vertx.codegen.util.CompanionMethodCreator; +import io.vertx.codegen.util.DefaultMethodCreator; +import io.vertx.codegen.util.MethodCreator; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.*; -import javax.lang.model.type.*; +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.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; -import javax.tools.Diagnostic; -import java.util.*; -import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; /** @@ -52,7 +77,6 @@ public class ClassModel implements Model { public static final String JSON_OBJECT = "io.vertx.core.json.JsonObject"; public static final String JSON_ARRAY = "io.vertx.core.json.JsonArray"; public static final String VERTX = "io.vertx.core.Vertx"; - private static final Logger logger = Logger.getLogger(ClassModel.class.getName()); protected final ProcessingEnvironment env; protected final AnnotationValueInfoFactory annotationValueInfoFactory; @@ -89,6 +113,9 @@ public class ClassModel implements Model { protected boolean deprecated; protected Text deprecatedDesc; + protected MethodCreator defaultMethodCreator; + protected MethodCreator companionMethodCreator; + public ClassModel(ProcessingEnvironment env, TypeElement modelElt) { this.elementUtils = env.getElementUtils(); this.typeUtils = env.getTypeUtils(); @@ -221,7 +248,7 @@ public List getAbstractSuperTypes() { } public TypeInfo getHandlerType() { - return (type.getKind() == ClassKind.API) ? ((ApiTypeInfo)type).getHandlerArg() : null; + return (type.getKind() == ClassKind.API) ? ((ApiTypeInfo) type).getHandlerArg() : null; } public Map> getMethodMap() { @@ -251,46 +278,11 @@ public Map> getMethodAnnotations() { } private void sortMethodMap(Map> map) { - for (List list: map.values()) { + for (List list : map.values()) { list.sort(Comparator.comparingInt(meth -> meth.getParams().size())); } } - protected void checkParamType(ExecutableElement elem, TypeMirror type, TypeInfo typeInfo, int pos, int numParams, boolean allowAnyJavaType) { - if (isLegalNonCallableParam(typeInfo, allowAnyJavaType)) { - return; - } - if (isLegalClassTypeParam(elem, typeInfo)) { - return; - } - if (isLegalHandlerType(typeInfo, allowAnyJavaType)) { - return; - } - if (isLegalHandlerAsyncResultType(typeInfo, allowAnyJavaType)) { - return; - } - if (isLegalFunctionType(typeInfo, allowAnyJavaType)) { - return; - } - throw new GenException(elem, "type " + typeInfo + " is not legal for use for a parameter in code generation"); - } - - protected void checkReturnType(ExecutableElement elem, TypeInfo type, TypeMirror typeMirror, boolean allowAnyJavaType) { - if (type.isVoid()) { - return; - } - if (isLegalNonCallableReturnType(type, allowAnyJavaType)) { - return; - } - if (isLegalHandlerType(type, allowAnyJavaType)) { - return; - } - if (isLegalHandlerAsyncResultType(type, allowAnyJavaType)) { - return; - } - throw new GenException(elem, "type " + type + " is not legal for use for a return type in code generation"); - } - protected void checkConstantType(VariableElement elem, TypeInfo type, TypeMirror typeMirror, boolean allowAnyJavaType) { if (isLegalNonCallableReturnType(type, allowAnyJavaType)) { return; @@ -339,117 +331,28 @@ private boolean isLegalEnum(TypeInfo info) { return info.getKind() == ClassKind.ENUM; } - /** - * The set Param - */ - private boolean isLegalNonCallableParam(TypeInfo typeInfo, boolean allowAnyJavaType) { - if (typeInfo.getKind().basic) { - return true; - } - if (typeInfo.getKind().json) { - return true; - } - if (isLegalDataObjectTypeParam(typeInfo)) { - return true; - } - if (isLegalEnum(typeInfo)) { - return true; - } - if (typeInfo.getKind() == ClassKind.THROWABLE) { - return true; - } - if (isTypeVariable(typeInfo)) { - return true; - } - if (typeInfo.getKind() == ClassKind.OBJECT) { - return true; - } - if (isVertxGenInterface(typeInfo, true)) { - return true; - } - if (allowAnyJavaType && typeInfo.getKind() == ClassKind.OTHER) { - return true; - } - if (isLegalContainerParam(typeInfo, allowAnyJavaType)) { - return true; - } - return false; - } - private boolean isTypeVariable(TypeInfo type) { return type instanceof TypeVariableInfo; } - private boolean isLegalDataObjectTypeParam(TypeInfo type) { - if (type.getKind() == ClassKind.DATA_OBJECT) { - DataObjectTypeInfo classType = (DataObjectTypeInfo) type; - return !classType.isAbstract(); - } - return false; - } - - private boolean isLegalClassTypeParam(ExecutableElement elt, TypeInfo type) { - if (type.getKind() == ClassKind.CLASS_TYPE && type.isParameterized()) { - ParameterizedTypeInfo parameterized = (ParameterizedTypeInfo) type; - TypeInfo arg = parameterized.getArg(0); - if (arg.isVariable()) { - TypeVariableInfo variable = (TypeVariableInfo) arg; - for (TypeParameterElement typeParamElt : elt.getTypeParameters()) { - if (typeParamElt.getSimpleName().toString().equals(variable.getName())) { - return true; - } - } - } - } - return false; - } - protected boolean isLegalDataObjectTypeReturn(TypeInfo type) { if (type.getKind() == ClassKind.DATA_OBJECT) { TypeElement typeElt = elementUtils.getTypeElement(type.getName()); if (typeElt != null) { Optional opt = elementUtils. - getAllMembers(typeElt). - stream(). - flatMap(Helper.FILTER_METHOD). - filter(m -> m.getSimpleName().toString().equals("toJson") && - m.getParameters().isEmpty() && - m.getReturnType().toString().equals(JSON_OBJECT)). - findFirst(); + getAllMembers(typeElt). + stream(). + flatMap(Helper.FILTER_METHOD). + filter(m -> m.getSimpleName().toString().equals("toJson") && + m.getParameters().isEmpty() && + m.getReturnType().toString().equals(JSON_OBJECT)). + findFirst(); return opt.isPresent(); } } return false; } - protected boolean isLegalContainerParam(TypeInfo type, boolean allowAnyJavaType) { - // List and Set are also legal for params if T = basic type, json, @VertxGen, @DataObject - // Map is also legal for returns and params if K is a String and V is a basic type, json, or a @VertxGen interface - if (rawTypeIs(type, List.class, Set.class, Map.class)) { - TypeInfo argument = ((ParameterizedTypeInfo) type).getArgs().get(0); - if (type.getKind() != ClassKind.MAP) { - if (isLegalArgumentContainerParam(argument, allowAnyJavaType) || - isLegalDataObjectTypeParam(argument) || - argument.getKind() == ClassKind.ENUM) { - return true; - } - } else if (argument.getKind() == ClassKind.STRING) { // Only allow Map's with String's for keys - argument = ((ParameterizedTypeInfo) type).getArgs().get(1); - return isLegalArgumentContainerParam(argument, allowAnyJavaType); - } - } - return false; - } - - private boolean isLegalArgumentContainerParam(TypeInfo argument, boolean allowAnyJavaType) { - ClassKind argumentKind = argument.getKind(); - return argumentKind.basic - || argumentKind.json - || isVertxGenInterface(argument, false) - || argumentKind == ClassKind.OBJECT - || (allowAnyJavaType && argumentKind == ClassKind.OTHER); - } - protected boolean isLegalContainerReturn(TypeInfo type, boolean allowAnyJavaType) { if (rawTypeIs(type, List.class, Set.class, Map.class)) { List args = ((ParameterizedTypeInfo) type).getArgs(); @@ -486,7 +389,7 @@ private boolean isVertxGenInterface(TypeInfo type, boolean allowParameterized) { for (TypeInfo paramType : parameterized.getArgs()) { ClassKind kind = paramType.getKind(); if (!(paramType instanceof ApiTypeInfo || paramType.isVariable() || kind == ClassKind.VOID - || kind.basic || kind.json || kind == ClassKind.DATA_OBJECT || kind == ClassKind.ENUM )) { + || kind.basic || kind.json || kind == ClassKind.DATA_OBJECT || kind == ClassKind.ENUM)) { return false; } if (paramType.isNullable()) { @@ -504,65 +407,24 @@ private boolean isVertxGenInterface(TypeInfo type, boolean allowParameterized) { return false; } - private boolean isLegalFunctionType(TypeInfo typeInfo, boolean allowAnyJavaType) { - if (typeInfo.getErased().getKind() == ClassKind.FUNCTION) { - TypeInfo paramType = ((ParameterizedTypeInfo) typeInfo).getArgs().get(0); - if (isLegalCallbackValueType(paramType, allowAnyJavaType) || paramType.getKind() == ClassKind.THROWABLE) { - TypeInfo returnType = ((ParameterizedTypeInfo) typeInfo).getArgs().get(1); - return isLegalNonCallableParam(returnType, allowAnyJavaType); - } - } - return false; - } - - private boolean isLegalHandlerType(TypeInfo type, boolean allowAnyJavaType) { - if (type.getErased().getKind() == ClassKind.HANDLER) { - TypeInfo eventType = ((ParameterizedTypeInfo) type).getArgs().get(0); - if (isLegalCallbackValueType(eventType, allowAnyJavaType) || eventType.getKind() == ClassKind.THROWABLE) { - return true; - } - } - return false; - } - - private boolean isLegalHandlerAsyncResultType(TypeInfo type, boolean allowAnyJavaType) { - if (type.getErased().getKind() == ClassKind.HANDLER) { - TypeInfo eventType = ((ParameterizedTypeInfo) type).getArgs().get(0); - if (eventType.getErased().getKind() == ClassKind.ASYNC_RESULT && !eventType.isNullable()) { - TypeInfo resultType = ((ParameterizedTypeInfo) eventType).getArgs().get(0); - if (isLegalCallbackValueType(resultType, allowAnyJavaType)) { - return true; - } - } - } - return false; - } - - private boolean isLegalCallbackValueType(TypeInfo type, boolean allowAnyJavaType) { - if (type.getKind() == ClassKind.VOID) { - return true; - } - return isLegalNonCallableReturnType(type, allowAnyJavaType); - } - private void determineApiTypes() { importedTypes = collectedTypes.stream(). - map(ClassTypeInfo::getRaw). - flatMap(Helper.instanceOf(ClassTypeInfo.class)). - filter(t -> !t.getPackageName().equals(ifaceFQCN)). - collect(Collectors.toSet()); + map(ClassTypeInfo::getRaw). + flatMap(Helper.instanceOf(ClassTypeInfo.class)). + filter(t -> !t.getPackageName().equals(ifaceFQCN)). + collect(Collectors.toSet()); referencedTypes = collectedTypes.stream(). - map(ClassTypeInfo::getRaw). - flatMap(Helper.instanceOf(ApiTypeInfo.class)). - filter(t -> !t.equals(type.getRaw())). - collect(Collectors.toSet()); + map(ClassTypeInfo::getRaw). + flatMap(Helper.instanceOf(ApiTypeInfo.class)). + filter(t -> !t.equals(type.getRaw())). + collect(Collectors.toSet()); referencedDataObjectTypes = collectedTypes.stream(). - map(ClassTypeInfo::getRaw). - flatMap(Helper.instanceOf(DataObjectTypeInfo.class)). - filter(t -> t.getKind() == ClassKind.DATA_OBJECT). - collect(Collectors.toSet()); + map(ClassTypeInfo::getRaw). + flatMap(Helper.instanceOf(DataObjectTypeInfo.class)). + filter(t -> t.getKind() == ClassKind.DATA_OBJECT). + collect(Collectors.toSet()); referencedEnumTypes = collectedTypes.stream(). map(ClassTypeInfo::getRaw). @@ -614,6 +476,10 @@ private void traverseType(Element elem) { ); deprecated = deprecated || deprecatedDesc != null; concrete = elem.getAnnotation(VertxGen.class) == null || elem.getAnnotation(VertxGen.class).concrete(); + + this.defaultMethodCreator = new DefaultMethodCreator(env, concrete); + this.companionMethodCreator = new CompanionMethodCreator(env, concrete); + DeclaredType tm = (DeclaredType) elem.asType(); List typeArgs = tm.getTypeArguments(); for (TypeMirror typeArg : typeArgs) { @@ -623,7 +489,7 @@ private void traverseType(Element elem) { } } List st = typeUtils.directSupertypes(tm); - for (TypeMirror tmSuper: st) { + for (TypeMirror tmSuper : st) { if (!tmSuper.toString().equals(Object.class.getName())) { TypeInfo superTypeInfo; try { @@ -658,18 +524,19 @@ private void traverseType(Element elem) { } } if (concreteSuperType != null && concreteSuperType.isParameterized()) { - tm = (DeclaredType) modelElt.asType();; + tm = (DeclaredType) modelElt.asType(); + ; st = typeUtils.directSupertypes(tm); - for (TypeMirror tmSuper: st) { + for (TypeMirror tmSuper : st) { if (tmSuper.getKind() == TypeKind.DECLARED) { DeclaredType abc = (DeclaredType) tmSuper; TypeElement tt = (TypeElement) abc.asElement(); if (tt.getQualifiedName().toString().equals(concreteSuperType.getRaw().getName())) { List list = new ArrayList<>(); int size = tt.getTypeParameters().size(); - for (int i = 0; i< size;i++) { + for (int i = 0; i < size; i++) { TypeMirror q = abc.getTypeArguments().get(i); - TypeInfo ti = typeFactory.create(q); + TypeInfo ti = typeFactory.create(q); list.add(ti); } superTypeArguments = list; @@ -681,6 +548,8 @@ private void traverseType(Element elem) { } } + + Optional companionOpt = CompanionHelper.getCompanion(elem); // Traverse nested elements that are not methods (like nested interfaces) for (Element enclosedElt : elem.getEnclosedElements()) { if (!isGenIgnore(enclosedElt)) { @@ -689,22 +558,33 @@ private void traverseType(Element elem) { case FIELD: // Allowed break; + case CLASS: { + if (companionOpt.isPresent()) { + break; + } + } default: throw new GenException(elem, "@VertxGen can only declare methods and not " + elem.asType().toString()); } } } + if (elem.getKind() == ElementKind.INTERFACE) { TypeMirror objectType = elementUtils.getTypeElement("java.lang.Object").asType(); + companionOpt.ifPresent(companion -> { + traverseMethods(companionMethodCreator, objectType, companion); + }); + // Traverse fields elementUtils.getAllMembers((TypeElement) elem).stream(). filter(elt -> !typeUtils.isSameType(elt.getEnclosingElement().asType(), objectType)). flatMap(Helper.FILTER_FIELD). forEach(elt -> { GenIgnore genIgnore = elt.getAnnotation(GenIgnore.class); + boolean isCompanionField = companionOpt.filter(companion -> elt.asType() == companion.asType()).isPresent(); boolean allowAnyJavaType; if (genIgnore != null) { if (!Arrays.asList(genIgnore.value()).contains(GenIgnore.PERMITTED_TYPE)) { @@ -715,7 +595,7 @@ private void traverseType(Element elem) { } else { allowAnyJavaType = false; } - ConstantInfo cst = fieldMethod(elt, allowAnyJavaType); + ConstantInfo cst = fieldMethod(elt, allowAnyJavaType || isCompanionField); if (cst != null) { cst.getType().collectImports(collectedTypes); constants.add(cst); @@ -723,38 +603,13 @@ private void traverseType(Element elem) { }); - // Traverse methods - elementUtils.getAllMembers((TypeElement) elem).stream(). - filter(elt -> !typeUtils.isSameType(elt.getEnclosingElement().asType(), objectType)). - flatMap(Helper.FILTER_METHOD). - forEach(elt -> { - GenIgnore genIgnore = elt.getAnnotation(GenIgnore.class); - boolean allowAnyJavaType; - if (genIgnore != null) { - if (!Arrays.asList(genIgnore.value()).contains(GenIgnore.PERMITTED_TYPE)) { - // Regular ignore - return; - } - allowAnyJavaType = true; - } else { - allowAnyJavaType = false; - } - MethodInfo meth = createMethod(elt, allowAnyJavaType); - if (meth != null) { - meth.collectImports(collectedTypes); - if (allowAnyJavaType) { - anyJavaTypeMethods.put(elt, meth); - } else { - methods.put(elt, meth); - } - } - }); + traverseMethods(defaultMethodCreator, objectType, modelElt); // Sort method map sortMethodMap(methodMap); // Now check for overloaded methods - for (List meths: methodMap.values()) { + for (List meths : methodMap.values()) { // Ambiguous try { @@ -774,6 +629,83 @@ private void traverseType(Element elem) { } } + private void traverseMethods(MethodCreator methodCreator, TypeMirror objectType, TypeElement element) { + elementUtils.getAllMembers(element). + stream(). + filter(elt -> !typeUtils.isSameType(elt.getEnclosingElement().asType(), objectType)). + flatMap(Helper.FILTER_METHOD). + forEach(elt -> { + GenIgnore genIgnore = elt.getAnnotation(GenIgnore.class); + boolean allowAnyJavaType; + if (genIgnore != null) { + if (!Arrays.asList(genIgnore.value()).contains(GenIgnore.PERMITTED_TYPE)) { + // Regular ignore + return; + } + allowAnyJavaType = true; + } else { + allowAnyJavaType = false; + } + Optional methodInfoOpt = methodCreator.createMethod(element, elt, allowAnyJavaType, collectedTypes, deprecatedDesc) + .filter(methodInfo -> { + // Check we don't hide another method, we don't check overrides but we are more + // interested by situations like diamond inheritance of the same method, in this case + // we see two methods with the same signature that don't override each other + for (Map.Entry otherMethod : methods.entrySet()) { + if (otherMethod.getValue().getName().equals(elt.getSimpleName().toString())) { + ExecutableType t1 = (ExecutableType) otherMethod.getKey().asType(); + ExecutableType t2 = (ExecutableType) elt.asType(); + if (typeUtils.isSubsignature(t1, t2) && typeUtils.isSubsignature(t2, t1)) { + otherMethod.getValue().getOwnerTypes().addAll(methodInfo.getOwnerTypes()); + return false; + } + } + } + return true; + }); + methodInfoOpt.filter(methodInfo -> !methodInfo.isContainingAnyJavaType()).ifPresent(methodInfo -> { + List methodsByName = methodMap.get(methodInfo.getName()); + if (methodsByName != null) { + // Overloaded methods must have same return type + for (MethodInfo method : methodsByName) { + if (!method.isContainingAnyJavaType() && !method.getReturnType().equals(methodInfo.getReturnType())) { + throw new GenException(this.modelElt, "Overloaded method " + methodInfo.getName() + " must have the same return type " + + method.getReturnType() + " != " + methodInfo.getReturnType()); + } + } + } else { + methodsByName = new ArrayList<>(); + methodMap.put(methodInfo.getName(), methodsByName); + methodAnnotationsMap.put(methodInfo.getName(), methodInfo.getAnnotations()); + } + methodsByName.add(methodInfo); + }); + methodInfoOpt + .filter(methodInfo -> { + TypeElement declaringElt = (TypeElement) elt.getEnclosingElement(); + TypeInfo declaringType = typeFactory.create(declaringElt.asType()); + ExecutableType methodType = (ExecutableType) typeUtils.asMemberOf((DeclaredType) modelElt.asType(), elt); + // Filter methods inherited from abstract ancestors + if (!declaringElt.equals(modelElt) && declaringType.getKind() == ClassKind.API) { + ApiTypeInfo declaringApiType = (ApiTypeInfo) declaringType.getRaw(); + if (declaringApiType.isConcrete()) { + return !typeUtils.isSameType(methodType, elt.asType()); + } + } + return true; + }) + .ifPresent(methodInfo -> { + methodInfo.collectImports(collectedTypes); + if (methodInfo.isContainingAnyJavaType()) { + anyJavaTypeMethods.put(elt, methodInfo); + } else { + methods.put(elt, methodInfo); + } + }); + + }); + } + private static boolean isGenIgnore(Element elt) { return elt.getAnnotation(GenIgnore.class) != null; } @@ -784,286 +716,22 @@ private ConstantInfo fieldMethod(VariableElement modelField, boolean allowAnyJav return null; } TypeInfo type = typeFactory.create(modelField.asType()); - checkConstantType(modelField, type, modelField.asType(),allowAnyJavaType); + checkConstantType(modelField, type, modelField.asType(), allowAnyJavaType); Doc doc = docFactory.createDoc(modelField); return new ConstantInfo(doc, modelField.getSimpleName().toString(), type); } - private MethodInfo createMethod(ExecutableElement modelMethod, boolean allowAnyJavaType) { - Set mods = modelMethod.getModifiers(); - if (!mods.contains(Modifier.PUBLIC)) { - return null; - } - - TypeElement declaringElt = (TypeElement) modelMethod.getEnclosingElement(); - TypeInfo declaringType = typeFactory.create(declaringElt.asType()); - - if (!declaringElt.equals(modelElt) && (declaringType.getKind() != ClassKind.API && declaringType.getKind() != ClassKind.HANDLER)) { - return null; - } - - ClassTypeInfo type = typeFactory.create(declaringElt.asType()).getRaw(); - - boolean isDefault = mods.contains(Modifier.DEFAULT); - boolean isStatic = mods.contains(Modifier.STATIC); - if (isStatic && !concrete) { - throw new GenException(modelMethod, "Abstract interface cannot declare static methods"); - } - - boolean isCacheReturn = modelMethod.getAnnotation(CacheReturn.class) != null; - ArrayList typeParams = new ArrayList<>(); - for (TypeParameterElement typeParam : modelMethod.getTypeParameters()) { - for (TypeMirror bound : typeParam.getBounds()) { - if (!isObjectBound(bound)) { - throw new GenException(modelMethod, "Type parameter bound not supported " + bound); - } - } - typeParams.add((TypeParamInfo.Method) TypeParamInfo.create(typeParam)); - } - - // - List modelMethods = new ArrayList<>(); - modelMethods.add(modelMethod); - - // Owner types - Set ownerTypes = new HashSet<>(); - ownerTypes.add(type); - - ArrayList ancestors = new ArrayList<>(Helper.resolveAncestorTypes(modelElt, true, true)); - - // Sort to have super types the last, etc.. - // solve some problem with diamond inheritance order that can show up in type use - Collections.sort(ancestors, (o1, o2) -> { - if (typeUtils.isSubtype(o1, o2)) { - return -1; - } else if (typeUtils.isSubtype(o2, o1)) { - return 1; - } else { - return ((TypeElement) o1.asElement()).getQualifiedName().toString().compareTo(((TypeElement) o2.asElement()).getQualifiedName().toString()); - } - }); - - // Check overrides and merge type use - for (DeclaredType ancestorType : ancestors) { - TypeElement ancestorElt = (TypeElement) ancestorType.asElement(); - if (ancestorElt.getAnnotation(VertxGen.class) != null) { - elementUtils.getAllMembers(ancestorElt). - stream(). - flatMap(Helper.FILTER_METHOD). - filter(meth -> elementUtils.overrides(modelMethod, meth, modelElt)). - forEach(overridenMethodElt -> { - modelMethods.add(overridenMethodElt); - ownerTypes.add(typeFactory.create((DeclaredType) ancestorElt.asType()).getRaw()); - }); - } - } - - // - Map paramDescs = new HashMap<>(); - String comment = elementUtils.getDocComment(modelMethod); - Doc doc = docFactory.createDoc(modelMethod); - Text returnDesc = null; - Text methodDeprecatedDesc = null; - if (doc != null) { - doc. - getBlockTags(). - stream(). - filter(tag -> tag.getName().equals("param")). - map(Tag.Param::new). - forEach(tag -> paramDescs.put(tag.getParamName(), tag.getParamDescription())); - Optional returnTag = doc. - getBlockTags(). - stream(). - filter(tag -> tag.getName().equals("return")). - findFirst(); - if (returnTag.isPresent()) { - returnDesc = new Text(Helper.normalizeWhitespaces(returnTag.get().getValue())).map(Token.tagMapper(elementUtils, typeUtils, modelElt)); - } - Optional methodDeprecatedTag = doc. - getBlockTags(). - stream(). - filter(tag -> tag.getName().equals("deprecated")). - findFirst(); - if (methodDeprecatedTag.isPresent()) { - methodDeprecatedDesc = new Text(Helper.normalizeWhitespaces(methodDeprecatedTag.get().getValue())).map(Token.tagMapper(elementUtils, typeUtils, modelElt)); - } - } - - // - List mParams = getParams(modelMethods, modelMethod, paramDescs, allowAnyJavaType); - - // - AnnotationMirror fluentAnnotation = Helper.resolveMethodAnnotation(Fluent.class, elementUtils, typeUtils, declaringElt, modelMethod); - boolean isFluent = fluentAnnotation != null; - if (isFluent) { - isFluent = true; - if (!typeUtils.isSameType(declaringElt.asType(), modelElt.asType())) { - String msg = "Interface " + modelElt + " does not redeclare the @Fluent return type " + - " of method " + modelMethod + " declared by " + declaringElt; - messager.printMessage(Diagnostic.Kind.WARNING, msg, modelElt, fluentAnnotation); - logger.warning(msg); - } else { - TypeMirror fluentType = modelMethod.getReturnType(); - if (!typeUtils.isAssignable(fluentType, modelElt.asType())) { - throw new GenException(modelMethod, "Methods marked with @Fluent must have a return type that extends the type"); - } - } - } - - // - TypeUse returnTypeUse = TypeUse.createReturnTypeUse(env, modelMethods.toArray(new ExecutableElement[modelMethods.size()])); - - ExecutableType methodType = (ExecutableType) typeUtils.asMemberOf((DeclaredType) modelElt.asType(), modelMethod); - TypeInfo returnType; - try { - returnType = typeFactory.create(returnTypeUse, methodType.getReturnType()); - } catch (Exception e) { - GenException genEx = new GenException(modelMethod, e.getMessage()); - genEx.initCause(e); - throw genEx; - } - returnType.collectImports(collectedTypes); - if (isCacheReturn && returnType.isVoid()) { - throw new GenException(modelMethod, "void method can't be marked with @CacheReturn"); - } - String methodName = modelMethod.getSimpleName().toString(); - - // Only check the return type if not fluent, because generated code won't look it at anyway - if (!isFluent) { - checkReturnType(modelMethod, returnType, methodType.getReturnType(), allowAnyJavaType); - } else if (returnType.isNullable()) { - throw new GenException(modelMethod, "Fluent return type cannot be nullable"); - } - - boolean methodDeprecated = modelMethod.getAnnotation(Deprecated.class) != null || deprecatedDesc != null; - - MethodInfo methodInfo = createMethodInfo( - ownerTypes, - methodName, - comment, - doc, - returnType, - returnDesc, - isFluent, - isCacheReturn, - mParams, - modelMethod, - isStatic, - isDefault, - typeParams, - declaringElt, - methodDeprecated, - methodDeprecatedDesc); - - // Check we don't hide another method, we don't check overrides but we are more - // interested by situations like diamond inheritance of the same method, in this case - // we see two methods with the same signature that don't override each other - for (Map.Entry otherMethod : methods.entrySet()) { - if (otherMethod.getValue().getName().equals(modelMethod.getSimpleName().toString())) { - ExecutableType t1 = (ExecutableType) otherMethod.getKey().asType(); - ExecutableType t2 = (ExecutableType) modelMethod.asType(); - if (typeUtils.isSubsignature(t1, t2) && typeUtils.isSubsignature(t2, t1)) { - otherMethod.getValue().getOwnerTypes().addAll(methodInfo.getOwnerTypes()); - return null; - } - } - } - - // Add the method to the method map (it's a bit ugly but useful for JS and Ruby) - if (!allowAnyJavaType) { - checkMethod(methodInfo); - List methodsByName = methodMap.get(methodInfo.getName()); - if (methodsByName == null) { - methodsByName = new ArrayList<>(); - methodMap.put(methodInfo.getName(), methodsByName); - methodAnnotationsMap.put(methodInfo.getName(), modelMethod.getAnnotationMirrors().stream().map(annotationValueInfoFactory::processAnnotation).collect(Collectors.toList())); - } - methodsByName.add(methodInfo); - } - - // Filter methods inherited from abstract ancestors - if (!declaringElt.equals(modelElt) && declaringType.getKind() == ClassKind.API) { - ApiTypeInfo declaringApiType = (ApiTypeInfo) declaringType.getRaw(); - if (declaringApiType.isConcrete()) { - if (typeUtils.isSameType(methodType, modelMethod.asType())) { - return null; - } - } - } - - return methodInfo; - } - - // This is a hook to allow a specific type of method to be created - protected MethodInfo createMethodInfo(Set ownerTypes, String methodName, String comment, Doc doc, TypeInfo returnType, - Text returnDescription, - boolean isFluent, boolean isCacheReturn, List mParams, - ExecutableElement methodElt, boolean isStatic, boolean isDefault, ArrayList typeParams, - TypeElement declaringElt, boolean methodDeprecated, Text methodDeprecatedDesc) { - return new MethodInfo(ownerTypes, methodName, returnType, returnDescription, - isFluent, isCacheReturn, mParams, comment, doc, isStatic, isDefault, typeParams, methodDeprecated, methodDeprecatedDesc); - } - - // This is a hook to allow different model implementations to check methods in different ways - protected void checkMethod(MethodInfo methodInfo) { - List methodsByName = methodMap.get(methodInfo.getName()); - if (methodsByName != null) { - // Overloaded methods must have same return type - for (MethodInfo meth: methodsByName) { - if (!meth.isContainingAnyJavaType() && !meth.getReturnType().equals(methodInfo.getReturnType())) { - throw new GenException(this.modelElt, "Overloaded method " + methodInfo.getName() + " must have the same return type " - + meth.getReturnType() + " != " + methodInfo.getReturnType()); - } - } - } - } - private boolean isObjectBound(TypeMirror bound) { return bound.getKind() == TypeKind.DECLARED && bound.toString().equals(Object.class.getName()); } - private List getParams(List modelMethods, - ExecutableElement methodElt, - Map descs, - boolean allowAnyJavaType) { - ExecutableType methodType = (ExecutableType) typeUtils.asMemberOf((DeclaredType) modelElt.asType(), methodElt); - ExecutableType methodType2 = (ExecutableType) methodElt.asType(); - List params = methodElt.getParameters(); - List mParams = new ArrayList<>(); - for (int i = 0; i < params.size();i++) { - VariableElement param = params.get(i); - TypeMirror type = methodType.getParameterTypes().get(i); - TypeInfo typeInfo; - TypeUse typeUse = TypeUse.createParamTypeUse(env, modelMethods.toArray(new ExecutableElement[modelMethods.size()]), i); - try { - typeInfo = typeFactory.create(typeUse, type); - } catch (Exception e) { - GenException ex = new GenException(param, e.getMessage()); - ex.setStackTrace(e.getStackTrace()); - throw ex; - } - checkParamType(methodElt, type, typeInfo, i, params.size(), allowAnyJavaType); - String name = param.getSimpleName().toString(); - String desc = descs.get(name); - Text text = desc != null ? new Text(desc).map(Token.tagMapper(elementUtils, typeUtils, modelElt)) : null; - TypeInfo unresolvedTypeInfo; - try { - unresolvedTypeInfo = typeFactory.create(typeUse, methodType2.getParameterTypes().get(i)); - } catch (Exception e) { - throw new GenException(param, e.getMessage()); - } - ParamInfo mParam = new ParamInfo(i, name, text, typeInfo, unresolvedTypeInfo); - mParams.add(mParam); - } - return mParams; - } - /** * @return {@code true} if the class has a {@code @Deprecated} annotation */ public boolean isDeprecated() { return deprecated; } + /** * @return the description of deprecated */ diff --git a/src/main/java/io/vertx/codegen/CodeGenProcessor.java b/src/main/java/io/vertx/codegen/CodeGenProcessor.java index 8a84092ce..f762efbc0 100644 --- a/src/main/java/io/vertx/codegen/CodeGenProcessor.java +++ b/src/main/java/io/vertx/codegen/CodeGenProcessor.java @@ -19,6 +19,17 @@ import java.io.Writer; import java.lang.annotation.Annotation; import java.util.*; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; @@ -236,11 +247,11 @@ public boolean process(Set annotations, RoundEnvironment } // Generate files generatedFiles.values().forEach(generated -> { - // todo: need to rewrite "/" according to platform file separator boolean shouldWarningsBeSuppressed = false; File file; - if (generated.uri.startsWith("/")) { - file = new File(generated.uri); + Path path = Paths.get(generated.uri); + if (path.isAbsolute()) { + file = path.toFile(); } else if (outputDirectory != null) { file = new File(outputDirectory, generated.uri); } else { diff --git a/src/main/java/io/vertx/codegen/CompanionHelper.java b/src/main/java/io/vertx/codegen/CompanionHelper.java new file mode 100644 index 000000000..b91a71000 --- /dev/null +++ b/src/main/java/io/vertx/codegen/CompanionHelper.java @@ -0,0 +1,55 @@ +package io.vertx.codegen; + +import io.vertx.codegen.annotations.GenIgnore; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; +import static javax.lang.model.element.ElementKind.CLASS; +import static javax.lang.model.element.ElementKind.FIELD; +import static javax.lang.model.element.Modifier.FINAL; +import static javax.lang.model.element.Modifier.STATIC; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class CompanionHelper { + + public static Optional getCompanion(Element rootElt) { + List enclosedElements = rootElt.getEnclosedElements(); + + Map companionCandidates = enclosedElements + .stream() + .filter((Element companionCandidate) -> companionCandidate.getKind() == CLASS) + .collect(toMap(Element::asType, Function.identity())); + + Set fieldCompanionCandidate = enclosedElements + .stream() + .filter((Element elt) -> elt.getKind() == FIELD && elt.getModifiers().containsAll(asList(STATIC, FINAL))) + .map(Element::asType).collect(toSet()); + + companionCandidates.keySet().retainAll(fieldCompanionCandidate); + + if (companionCandidates.size() > 1) { + throw new RuntimeException(); + } + Iterator iter = companionCandidates.values().iterator(); + if (iter.hasNext()) { + return Optional.of(iter.next()) + .map(TypeElement.class::cast) + .filter(elt -> elt.getAnnotation(GenIgnore.class) == null); + } + return Optional.empty(); + } + +} diff --git a/src/main/java/io/vertx/codegen/MethodInfo.java b/src/main/java/io/vertx/codegen/MethodInfo.java index 1aad15c52..4c56e475e 100644 --- a/src/main/java/io/vertx/codegen/MethodInfo.java +++ b/src/main/java/io/vertx/codegen/MethodInfo.java @@ -18,13 +18,21 @@ import io.vertx.codegen.doc.Doc; import io.vertx.codegen.doc.Text; +import io.vertx.codegen.type.AnnotationValueInfo; import io.vertx.codegen.type.ClassKind; import io.vertx.codegen.type.ClassTypeInfo; import io.vertx.codegen.type.ParameterizedTypeInfo; import io.vertx.codegen.type.TypeInfo; import io.vertx.codegen.type.TypeVariableInfo; -import java.util.*; +import javax.lang.model.element.Name; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; /** * @author Tim Fox @@ -45,6 +53,8 @@ public class MethodInfo implements Comparable { private List params; private boolean deprecated; private Text deprecatedDesc; + private Name companion; + private List annotations; public MethodInfo(Set ownerTypes, String name, TypeInfo returnType, Text returnDescription, boolean fluent, boolean cacheReturn, @@ -285,6 +295,19 @@ public MethodInfo setDefaultMethod(boolean defaultMethod) { return this; } + public String getCompanion() { + return Objects.toString(companion); + } + + public MethodInfo setCompanion(Name companionObjectName) { + this.companion = companionObjectName; + return this; + } + + public boolean isCompanionMethod() { + return companion != null; + } + /** * * @return {@code true} if the method has a {@code @Deprecated} annotation @@ -319,6 +342,15 @@ public MethodInfo setTypeParams(List typeParams) { return this; } + public List getAnnotations() { + return annotations; + } + + public MethodInfo setAnnotations(List annotations) { + this.annotations = annotations; + return this; + } + public void mergeTypeParams(List mergedTypeParams) throws IllegalArgumentException { int l = Math.min(typeParams.size(), mergedTypeParams.size()); if (typeParams.subList(0, l).equals(mergedTypeParams.subList(0, l))) { diff --git a/src/main/java/io/vertx/codegen/service/method/checker/AllowParameterizedVertxGenInterfaceChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/AllowParameterizedVertxGenInterfaceChecker.java new file mode 100644 index 000000000..2713e9242 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/AllowParameterizedVertxGenInterfaceChecker.java @@ -0,0 +1,41 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ApiTypeInfo; +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.ParameterizedTypeInfo; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class AllowParameterizedVertxGenInterfaceChecker implements Checker { + + public static Checker getInstance() { + return new AllowParameterizedVertxGenInterfaceChecker(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + if (type.getKind() == ClassKind.API) { + if (type.isParameterized()) { + ParameterizedTypeInfo parameterized = (ParameterizedTypeInfo) type; + for (TypeInfo paramType : parameterized.getArgs()) { + ClassKind kind = paramType.getKind(); + if (!(paramType instanceof ApiTypeInfo || paramType.isVariable() || kind == ClassKind.VOID + || kind.basic || kind.json || kind == ClassKind.DATA_OBJECT || kind == ClassKind.ENUM)) { + return false; + } + if (paramType.isNullable()) { + return false; + } + } + return true; + } else { + return true; + } + } + return false; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/Checker.java b/src/main/java/io/vertx/codegen/service/method/checker/Checker.java new file mode 100644 index 000000000..6cd1a4db3 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/Checker.java @@ -0,0 +1,15 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +@FunctionalInterface +public interface Checker { + + boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType); + +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalArgumentContainerParamChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalArgumentContainerParamChecker.java new file mode 100644 index 000000000..c1f263f59 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalArgumentContainerParamChecker.java @@ -0,0 +1,36 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalArgumentContainerParamChecker implements Checker { + + private final Checker vertxGenInterfaceChecker; + + public static Checker getInstance() { + return new LegalArgumentContainerParamChecker(); + } + + public LegalArgumentContainerParamChecker(Checker vertxGenInterfaceChecker) { + this.vertxGenInterfaceChecker = vertxGenInterfaceChecker; + } + + public LegalArgumentContainerParamChecker() { + this.vertxGenInterfaceChecker = NotAllowParameterizedVertxGenInterfaceChecker.getInstance(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + ClassKind argumentKind = type.getKind(); + return argumentKind.basic + || argumentKind.json + || vertxGenInterfaceChecker.check(elt, type, false) + || argumentKind == ClassKind.OBJECT + || (allowAnyJavaType && argumentKind == ClassKind.OTHER); + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalArgumentContainerReturnChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalArgumentContainerReturnChecker.java new file mode 100644 index 000000000..d1c02d68b --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalArgumentContainerReturnChecker.java @@ -0,0 +1,25 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalArgumentContainerReturnChecker implements Checker { + + public static Checker getInstance() { + return new LegalArgumentContainerReturnChecker(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + ClassKind argumentKind = type.getKind(); + return argumentKind.basic + || argumentKind.json + || argumentKind == ClassKind.OBJECT + || (allowAnyJavaType && argumentKind == ClassKind.OTHER); + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalCallbackValueTypeChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalCallbackValueTypeChecker.java new file mode 100644 index 000000000..45e77d4f7 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalCallbackValueTypeChecker.java @@ -0,0 +1,35 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalCallbackValueTypeChecker implements Checker { + + private final Checker legalNonCallableReturnType; + + public static Checker getInstance(Elements elementUtils) { + return new LegalCallbackValueTypeChecker(elementUtils); + } + + public LegalCallbackValueTypeChecker(Checker legalNonCallableReturnType) { + this.legalNonCallableReturnType = legalNonCallableReturnType; + } + + public LegalCallbackValueTypeChecker(Elements elementUtils) { + this.legalNonCallableReturnType = LegalNonCallableReturnTypeChecker.getInstance(elementUtils); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + if (type.getKind() == ClassKind.VOID) { + return true; + } + return legalNonCallableReturnType.check(elt, type, allowAnyJavaType); + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalClassTypeParamChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalClassTypeParamChecker.java new file mode 100644 index 000000000..79694fb56 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalClassTypeParamChecker.java @@ -0,0 +1,36 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.ParameterizedTypeInfo; +import io.vertx.codegen.type.TypeInfo; +import io.vertx.codegen.type.TypeVariableInfo; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeParameterElement; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalClassTypeParamChecker implements Checker { + + public static Checker getInstance() { + return new LegalClassTypeParamChecker(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + if (type.getKind() == ClassKind.CLASS_TYPE && type.isParameterized()) { + ParameterizedTypeInfo parameterized = (ParameterizedTypeInfo) type; + TypeInfo arg = parameterized.getArg(0); + if (arg.isVariable()) { + TypeVariableInfo variable = (TypeVariableInfo) arg; + for (TypeParameterElement typeParamElt : elt.getTypeParameters()) { + if (typeParamElt.getSimpleName().toString().equals(variable.getName())) { + return true; + } + } + } + } + return false; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerParamChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerParamChecker.java new file mode 100644 index 000000000..0fc2a6c78 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerParamChecker.java @@ -0,0 +1,56 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.ParameterizedTypeInfo; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static io.vertx.codegen.util.ModelUtils.rawTypeIs; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalContainerParamChecker implements Checker { + + private final Checker legalArgumentContainerParamChecker; + private final Checker legalDataObjectTypeParamChecker; + + public static Checker getInstance() { + return new LegalContainerParamChecker(); + } + + public LegalContainerParamChecker(Checker legalArgumentContainerParamChecker, Checker legalDataObjectTypeParamChecker) { + this.legalArgumentContainerParamChecker = legalArgumentContainerParamChecker; + this.legalDataObjectTypeParamChecker = legalDataObjectTypeParamChecker; + } + + + public LegalContainerParamChecker() { + this.legalArgumentContainerParamChecker = LegalArgumentContainerParamChecker.getInstance(); + this.legalDataObjectTypeParamChecker = LegalDataObjectTypeParamChecker.getInstance(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + // List and Set are also legal for params if T = basic type, json, @VertxGen, @DataObject + // Map is also legal for returns and params if K is a String and V is a basic type, json, or a @VertxGen interface + if (rawTypeIs(type, List.class, Set.class, Map.class)) { + TypeInfo argument = ((ParameterizedTypeInfo) type).getArgs().get(0); + if (type.getKind() != ClassKind.MAP) { + if (legalArgumentContainerParamChecker.check(elt, argument, allowAnyJavaType) || + legalDataObjectTypeParamChecker.check(elt, argument, false) || + argument.getKind() == ClassKind.ENUM) { + return true; + } + } else if (argument.getKind() == ClassKind.STRING) { + argument = ((ParameterizedTypeInfo) type).getArgs().get(1); + return legalArgumentContainerParamChecker.check(elt, argument, allowAnyJavaType); + } + } + return false; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerReturnChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerReturnChecker.java new file mode 100644 index 000000000..be4e8bf1b --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerReturnChecker.java @@ -0,0 +1,62 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.ParameterizedTypeInfo; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static io.vertx.codegen.util.ModelUtils.rawTypeIs; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalContainerReturnChecker implements Checker { + + private final Checker vertxGenInterfaceChecker; + private final Checker legalDataObjectTypeReturnChecker; + private final Checker legalArgumentContainerReturn; + + public static Checker getInstance(Elements elementsUtils) { + return new LegalContainerReturnChecker(elementsUtils); + } + + public LegalContainerReturnChecker(Checker vertxGenInterfaceChecker, Checker legalDataObjectTypeReturnChecker, Checker legalArgumentContainerReturn) { + this.vertxGenInterfaceChecker = vertxGenInterfaceChecker; + this.legalDataObjectTypeReturnChecker = legalDataObjectTypeReturnChecker; + this.legalArgumentContainerReturn = legalArgumentContainerReturn; + } + + + public LegalContainerReturnChecker(Elements elementsUtils) { + this.vertxGenInterfaceChecker = NotAllowParameterizedVertxGenInterfaceChecker.getInstance(); + this.legalDataObjectTypeReturnChecker = LegalDataObjectTypeReturnChecker.getInstance(elementsUtils); + this.legalArgumentContainerReturn = LegalArgumentContainerReturnChecker.getInstance(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + + if (rawTypeIs(type, List.class, Set.class, Map.class)) { + List args = ((ParameterizedTypeInfo) type).getArgs(); + if (type.getKind() == ClassKind.MAP) { + if (args.get(0).getKind() != ClassKind.STRING) { + return false; + } + TypeInfo valueType = args.get(1); + return legalArgumentContainerReturn.check(elt, valueType, allowAnyJavaType); + } else { + TypeInfo valueType = args.get(0); + return legalArgumentContainerReturn.check(elt, valueType, allowAnyJavaType) || + valueType.getKind() == ClassKind.ENUM || + vertxGenInterfaceChecker.check(elt, valueType, false) || + legalDataObjectTypeReturnChecker.check(elt, valueType, false); + } + } + return false; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalDataObjectTypeParamChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalDataObjectTypeParamChecker.java new file mode 100644 index 000000000..2253bf1ab --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalDataObjectTypeParamChecker.java @@ -0,0 +1,26 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.DataObjectTypeInfo; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalDataObjectTypeParamChecker implements Checker { + + public static Checker getInstance() { + return new LegalDataObjectTypeParamChecker(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + if (type.getKind() == ClassKind.DATA_OBJECT) { + DataObjectTypeInfo classType = (DataObjectTypeInfo) type; + return !classType.isAbstract(); + } + return false; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalDataObjectTypeReturnChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalDataObjectTypeReturnChecker.java new file mode 100644 index 000000000..2b8264e7a --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalDataObjectTypeReturnChecker.java @@ -0,0 +1,46 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.Helper; +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.TypeInfo; +import io.vertx.core.json.JsonObject; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; +import java.util.Optional; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalDataObjectTypeReturnChecker implements Checker { + + private final Elements elementUtils; + + public static Checker getInstance(Elements elementUtils) { + return new LegalDataObjectTypeReturnChecker(elementUtils); + } + + public LegalDataObjectTypeReturnChecker(Elements elementUtils) { + this.elementUtils = elementUtils; + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + if (type.getKind() == ClassKind.DATA_OBJECT) { + TypeElement typeElt = elementUtils.getTypeElement(type.getName()); + if (typeElt != null) { + Optional opt = elementUtils. + getAllMembers(typeElt). + stream(). + flatMap(Helper.FILTER_METHOD). + filter(m -> m.getSimpleName().toString().equals("toJson") && + m.getParameters().isEmpty() && + m.getReturnType().toString().equals(JsonObject.class.getCanonicalName())). + findFirst(); + return opt.isPresent(); + } + } + return false; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalEnumChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalEnumChecker.java new file mode 100644 index 000000000..de00507d8 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalEnumChecker.java @@ -0,0 +1,21 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalEnumChecker implements Checker { + + public static Checker getInstance() { + return new LegalEnumChecker(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + return type.getKind() == ClassKind.ENUM; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalFunctionTypeChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalFunctionTypeChecker.java new file mode 100644 index 000000000..22bd457c8 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalFunctionTypeChecker.java @@ -0,0 +1,43 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.ParameterizedTypeInfo; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalFunctionTypeChecker implements Checker { + + private final Checker legalCallbackValueType; + private final Checker legalNonCallableParam; + + public static Checker getInstance(Elements elementUtils) { + return new LegalFunctionTypeChecker(elementUtils); + } + + public LegalFunctionTypeChecker(Checker legalCallbackValueType, Checker legalNonCallableParam) { + this.legalCallbackValueType = legalCallbackValueType; + this.legalNonCallableParam = legalNonCallableParam; + } + + public LegalFunctionTypeChecker(Elements elementUtils) { + this.legalCallbackValueType = LegalCallbackValueTypeChecker.getInstance(elementUtils); + this.legalNonCallableParam = LegalNonCallableParamChecker.getInstance(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + if (type.getErased().getKind() == ClassKind.FUNCTION) { + TypeInfo paramType = ((ParameterizedTypeInfo) type).getArgs().get(0); + if (legalCallbackValueType.check(elt, paramType, allowAnyJavaType) || paramType.getKind() == ClassKind.THROWABLE) { + TypeInfo returnType = ((ParameterizedTypeInfo) type).getArgs().get(1); + return legalNonCallableParam.check(elt, returnType, allowAnyJavaType); + } + } + return false; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalHandlerAsyncResultTypeChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalHandlerAsyncResultTypeChecker.java new file mode 100644 index 000000000..29aa829a4 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalHandlerAsyncResultTypeChecker.java @@ -0,0 +1,40 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.ParameterizedTypeInfo; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalHandlerAsyncResultTypeChecker implements Checker { + + private final Checker legalCallbackValueType; + + public static Checker getInstance(Elements elementUtils) { + return new LegalHandlerAsyncResultTypeChecker(elementUtils); + } + + public LegalHandlerAsyncResultTypeChecker(Checker legalCallbackValueType) { + this.legalCallbackValueType = legalCallbackValueType; + } + + public LegalHandlerAsyncResultTypeChecker(Elements elementUtils) { + this.legalCallbackValueType = LegalCallbackValueTypeChecker.getInstance(elementUtils); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + if (type.getErased().getKind() == ClassKind.HANDLER) { + TypeInfo eventType = ((ParameterizedTypeInfo) type).getArgs().get(0); + if (eventType.getErased().getKind() == ClassKind.ASYNC_RESULT && !eventType.isNullable()) { + TypeInfo resultType = ((ParameterizedTypeInfo) eventType).getArgs().get(0); + return legalCallbackValueType.check(elt, resultType, allowAnyJavaType); + } + } + return false; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalHandlerTypeChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalHandlerTypeChecker.java new file mode 100644 index 000000000..08c802f81 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalHandlerTypeChecker.java @@ -0,0 +1,37 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.ParameterizedTypeInfo; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalHandlerTypeChecker implements Checker { + + private final Checker legalCallbackValueType; + + public static Checker getInstance(Elements elementUtils) { + return new LegalHandlerTypeChecker(elementUtils); + } + + public LegalHandlerTypeChecker(Checker legalCallbackValueType) { + this.legalCallbackValueType = legalCallbackValueType; + } + + public LegalHandlerTypeChecker(Elements elementUtils) { + this.legalCallbackValueType = LegalCallbackValueTypeChecker.getInstance(elementUtils); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + if (type.getErased().getKind() == ClassKind.HANDLER) { + TypeInfo eventType = ((ParameterizedTypeInfo) type).getArgs().get(0); + return legalCallbackValueType.check(elt, eventType, allowAnyJavaType) || eventType.getKind() == ClassKind.THROWABLE; + } + return false; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalNonCallableParamChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalNonCallableParamChecker.java new file mode 100644 index 000000000..94133874f --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalNonCallableParamChecker.java @@ -0,0 +1,45 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalNonCallableParamChecker implements Checker { + + private final List delegates; + + public static Checker getInstance() { + return new LegalNonCallableParamChecker(); + } + + public LegalNonCallableParamChecker(List delegates) { + this.delegates = delegates; + } + + public LegalNonCallableParamChecker() { + this.delegates = asList( + (elt, type, allowAnyJavaType) -> type.getKind().basic, + (elt, type, allowAnyJavaType) -> type.getKind().json, + LegalDataObjectTypeParamChecker.getInstance(), + LegalEnumChecker.getInstance(), + (elt, type, allowAnyJavaType) -> type.getKind() == ClassKind.THROWABLE, + TypeVariableChecker.getInstance(), + (elt, type, allowAnyJavaType) -> type.getKind() == ClassKind.OBJECT, + AllowParameterizedVertxGenInterfaceChecker.getInstance(), + (elt, type, allowAnyJavaType) -> allowAnyJavaType && type.getKind() == ClassKind.OTHER, + LegalContainerParamChecker.getInstance() + ); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + return delegates.stream().anyMatch(checker -> checker.check(elt, type, allowAnyJavaType)); + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/LegalNonCallableReturnTypeChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalNonCallableReturnTypeChecker.java new file mode 100644 index 000000000..8f3d47979 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalNonCallableReturnTypeChecker.java @@ -0,0 +1,46 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class LegalNonCallableReturnTypeChecker implements Checker { + + private final List delegates; + + public static Checker getInstance(Elements elementUtils) { + return new LegalNonCallableReturnTypeChecker(elementUtils); + } + + public LegalNonCallableReturnTypeChecker(List delegates) { + this.delegates = delegates; + } + + public LegalNonCallableReturnTypeChecker(Elements elementUtils) { + this.delegates = asList( + (elt, type, allowAnyJavaType) -> type.getKind().basic, + (elt, type, allowAnyJavaType) -> type.getKind().json, + LegalDataObjectTypeReturnChecker.getInstance(elementUtils), + LegalEnumChecker.getInstance(), + (elt, type, allowAnyJavaType) -> type.getKind() == ClassKind.THROWABLE, + TypeVariableChecker.getInstance(), + (elt, type, allowAnyJavaType) -> type.getKind() == ClassKind.OBJECT, + AllowParameterizedVertxGenInterfaceChecker.getInstance(), + (elt, type, allowAnyJavaType) -> allowAnyJavaType && type.getKind() == ClassKind.OTHER, + LegalContainerReturnChecker.getInstance(elementUtils) + ); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + return delegates.stream().anyMatch(checker -> checker.check(elt, type, allowAnyJavaType)); + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/NotAllowParameterizedVertxGenInterfaceChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/NotAllowParameterizedVertxGenInterfaceChecker.java new file mode 100644 index 000000000..bf5266ea0 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/NotAllowParameterizedVertxGenInterfaceChecker.java @@ -0,0 +1,24 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class NotAllowParameterizedVertxGenInterfaceChecker implements Checker { + + public static Checker getInstance() { + return new NotAllowParameterizedVertxGenInterfaceChecker(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + if (type.getKind() == ClassKind.API) { + return !type.isParameterized(); + } + return false; + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/ParamTypeChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/ParamTypeChecker.java new file mode 100644 index 000000000..ff8920640 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/ParamTypeChecker.java @@ -0,0 +1,40 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class ParamTypeChecker implements Checker { + + private final List delegates; + + public static Checker getInstance(Elements elementUtils) { + return new ParamTypeChecker(elementUtils); + } + + public ParamTypeChecker(List delegates) { + this.delegates = delegates; + } + + public ParamTypeChecker(Elements elementUtils) { + this.delegates = asList( + LegalNonCallableParamChecker.getInstance(), + LegalClassTypeParamChecker.getInstance(), + LegalHandlerTypeChecker.getInstance(elementUtils), + LegalHandlerAsyncResultTypeChecker.getInstance(elementUtils), + LegalFunctionTypeChecker.getInstance(elementUtils) + ); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + return delegates.stream().anyMatch(checker -> checker.check(elt, type, allowAnyJavaType)); + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/ReturnTypeChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/ReturnTypeChecker.java new file mode 100644 index 000000000..bbd13af3a --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/ReturnTypeChecker.java @@ -0,0 +1,39 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class ReturnTypeChecker implements Checker { + + private final List delegates; + + public static Checker getInstance(Elements elementUtils) { + return new ReturnTypeChecker(elementUtils); + } + + public ReturnTypeChecker(List delegates) { + this.delegates = delegates; + } + + public ReturnTypeChecker(Elements elementUtils) { + this.delegates = asList( + (elt, type, allowAnyJavaType) -> type.isVoid(), + LegalNonCallableReturnTypeChecker.getInstance(elementUtils), + LegalHandlerTypeChecker.getInstance(elementUtils), + LegalHandlerAsyncResultTypeChecker.getInstance(elementUtils) + ); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + return delegates.stream().anyMatch(checker -> checker.check(elt, type, allowAnyJavaType)); + } +} diff --git a/src/main/java/io/vertx/codegen/service/method/checker/TypeVariableChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/TypeVariableChecker.java new file mode 100644 index 000000000..a39db5717 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/TypeVariableChecker.java @@ -0,0 +1,21 @@ +package io.vertx.codegen.service.method.checker; + +import io.vertx.codegen.type.TypeInfo; +import io.vertx.codegen.type.TypeVariableInfo; + +import javax.lang.model.element.ExecutableElement; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class TypeVariableChecker implements Checker { + + public static Checker getInstance() { + return new TypeVariableChecker(); + } + + @Override + public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaType) { + return type instanceof TypeVariableInfo; + } +} diff --git a/src/main/java/io/vertx/codegen/util/CompanionMethodCreator.java b/src/main/java/io/vertx/codegen/util/CompanionMethodCreator.java new file mode 100644 index 000000000..da0f92675 --- /dev/null +++ b/src/main/java/io/vertx/codegen/util/CompanionMethodCreator.java @@ -0,0 +1,296 @@ +package io.vertx.codegen.util; + +import io.vertx.codegen.GenException; +import io.vertx.codegen.Helper; +import io.vertx.codegen.MethodInfo; +import io.vertx.codegen.ParamInfo; +import io.vertx.codegen.TypeParamInfo; +import io.vertx.codegen.annotations.CacheReturn; +import io.vertx.codegen.annotations.Fluent; +import io.vertx.codegen.annotations.VertxGen; +import io.vertx.codegen.doc.Doc; +import io.vertx.codegen.doc.Tag; +import io.vertx.codegen.doc.Text; +import io.vertx.codegen.doc.Token; +import io.vertx.codegen.service.method.checker.Checker; +import io.vertx.codegen.service.method.checker.ParamTypeChecker; +import io.vertx.codegen.service.method.checker.ReturnTypeChecker; +import io.vertx.codegen.type.AnnotationValueInfo; +import io.vertx.codegen.type.AnnotationValueInfoFactory; +import io.vertx.codegen.type.ClassTypeInfo; +import io.vertx.codegen.type.TypeInfo; +import io.vertx.codegen.type.TypeMirrorFactory; +import io.vertx.codegen.type.TypeUse; + +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +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.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static io.vertx.codegen.util.ModelUtils.isObjectBound; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class CompanionMethodCreator implements MethodCreator { + + private static final Logger logger = Logger.getLogger(CompanionMethodCreator.class.getName()); + + protected final ProcessingEnvironment env; + protected final AnnotationValueInfoFactory annotationValueInfoFactory; + protected final Messager messager; + protected final TypeMirrorFactory typeFactory; + protected final Elements elementUtils; + protected final Types typeUtils; + protected boolean deprecated; + protected final boolean concrete; + + protected final Checker returnTypeChecker; + protected final Checker paramTypeChecker; + + public CompanionMethodCreator(ProcessingEnvironment env, boolean concrete) { + this.elementUtils = env.getElementUtils(); + this.typeUtils = env.getTypeUtils(); + this.env = env; + this.concrete = concrete; + this.typeFactory = new TypeMirrorFactory(elementUtils, typeUtils); + this.messager = env.getMessager(); + this.annotationValueInfoFactory = new AnnotationValueInfoFactory(typeFactory); + this.returnTypeChecker = ReturnTypeChecker.getInstance(elementUtils); + this.paramTypeChecker = ParamTypeChecker.getInstance(elementUtils); + + } + + @Override + public Optional createMethod(TypeElement companion, ExecutableElement modelMethod, boolean allowAnyJavaType, Set collectedTypes, Text deprecatedDesc) { + + Doc.Factory docFactory = new Doc.Factory(env.getMessager(), elementUtils, typeUtils, typeFactory, companion); + + Set mods = modelMethod.getModifiers(); + if (!mods.contains(Modifier.PUBLIC)) { + return Optional.empty(); + } + + ClassTypeInfo type = typeFactory.create(companion.getEnclosingElement().asType()).getRaw(); + + boolean isStatic = true; + + boolean isCacheReturn = modelMethod.getAnnotation(CacheReturn.class) != null; + ArrayList typeParams = new ArrayList<>(); + for (TypeParameterElement typeParam : modelMethod.getTypeParameters()) { + for (TypeMirror bound : typeParam.getBounds()) { + if (!isObjectBound(bound)) { + throw new GenException(modelMethod, "Type parameter bound not supported " + bound); + } + } + typeParams.add((TypeParamInfo.Method) TypeParamInfo.create(typeParam)); + } + + // + List modelMethods = new ArrayList<>(); + modelMethods.add(modelMethod); + + // Owner types + Set ownerTypes = new HashSet<>(); + ownerTypes.add(type); + + List ancestors = new ArrayList<>(Helper.resolveAncestorTypes(companion, true, true)); + + // Sort to have super types the last, etc.. + // solve some problem with diamond inheritance order that can show up in type use + ancestors.sort((o1, o2) -> { + if (typeUtils.isSubtype(o1, o2)) { + return -1; + } else if (typeUtils.isSubtype(o2, o1)) { + return 1; + } else { + return ((TypeElement) o1.asElement()).getQualifiedName().toString().compareTo(((TypeElement) o2.asElement()).getQualifiedName().toString()); + } + }); + + // Check overrides and merge type use + for (DeclaredType ancestorType : ancestors) { + TypeElement ancestorElt = (TypeElement) ancestorType.asElement(); + if (ancestorElt.getAnnotation(VertxGen.class) != null) { + elementUtils.getAllMembers(ancestorElt). + stream(). + flatMap(Helper.FILTER_METHOD). + filter(meth -> elementUtils.overrides(modelMethod, meth, companion)). + forEach(overridenMethodElt -> { + modelMethods.add(overridenMethodElt); + ownerTypes.add(typeFactory.create((DeclaredType) ancestorElt.asType()).getRaw()); + }); + } + } + + // + Map paramDescs = new HashMap<>(); + String comment = elementUtils.getDocComment(modelMethod); + Doc doc = docFactory.createDoc(modelMethod); + Text returnDesc = null; + Text methodDeprecatedDesc = null; + if (doc != null) { + doc. + getBlockTags(). + stream(). + filter(tag -> tag.getName().equals("param")). + map(Tag.Param::new). + forEach(tag -> paramDescs.put(tag.getParamName(), tag.getParamDescription())); + Optional returnTag = doc. + getBlockTags(). + stream(). + filter(tag -> tag.getName().equals("return")). + findFirst(); + if (returnTag.isPresent()) { + returnDesc = new Text(Helper.normalizeWhitespaces(returnTag.get().getValue())).map(Token.tagMapper(elementUtils, typeUtils, companion)); + } + Optional methodDeprecatedTag = doc. + getBlockTags(). + stream(). + filter(tag -> tag.getName().equals("deprecated")). + findFirst(); + if (methodDeprecatedTag.isPresent()) { + methodDeprecatedDesc = new Text(Helper.normalizeWhitespaces(methodDeprecatedTag.get().getValue())).map(Token.tagMapper(elementUtils, typeUtils, companion)); + } + } + + // + List mParams = getCompanionMethodParams(companion, modelMethods, modelMethod, paramDescs, allowAnyJavaType); + + // + AnnotationMirror fluentAnnotation = Helper.resolveMethodAnnotation(Fluent.class, elementUtils, typeUtils, companion, modelMethod); +// boolean isFluent = fluentAnnotation != null; +// if (isFluent) { +// if (!typeUtils.isSameType(companion.asType(), modelElt.asType())) { +// String msg = "Interface " + modelElt + " does not redeclare the @Fluent return type " + +// " of method " + modelMethod + " declared by " + companion; +// messager.printMessage(Diagnostic.Kind.WARNING, msg, modelElt, fluentAnnotation); +// logger.warning(msg); +// } else { +// TypeMirror fluentType = modelMethod.getReturnType(); +// if (!typeUtils.isAssignable(fluentType, modelElt.asType())) { +// throw new GenException(modelMethod, "Methods marked with @Fluent must have a return type that extends the type"); +// } +// } +// } + + // + TypeUse returnTypeUse = TypeUse.createReturnTypeUse(env, modelMethods.toArray(new ExecutableElement[0])); + + ExecutableType methodType = (ExecutableType) typeUtils.asMemberOf((DeclaredType) companion.asType(), modelMethod); + TypeInfo returnType; + try { + returnType = typeFactory.create(returnTypeUse, methodType.getReturnType()); + } catch (Exception e) { + GenException genEx = new GenException(modelMethod, e.getMessage()); + genEx.initCause(e); + throw genEx; + } + returnType.collectImports(collectedTypes); + if (isCacheReturn && returnType.isVoid()) { + throw new GenException(modelMethod, "void method can't be marked with @CacheReturn"); + } + String methodName = modelMethod.getSimpleName().toString(); + + // Only check the return type if not fluent, because generated code won't look it at anyway + if (!returnTypeChecker.check(modelMethod, returnType, allowAnyJavaType)) { + throw new GenException(modelMethod, "type " + returnType + " is not legal for use for a return type in code generation"); + } + + + boolean methodDeprecated = modelMethod.getAnnotation(Deprecated.class) != null || deprecatedDesc != null; + + MethodInfo methodInfo = createMethodInfo( + ownerTypes, + methodName, + comment, + doc, + returnType, + returnDesc, + false, + isCacheReturn, + mParams, + modelMethod, + isStatic, + false, + typeParams, + companion, + methodDeprecated, + methodDeprecatedDesc).setCompanion(companion.getSimpleName()); + + List annotations = modelMethod.getAnnotationMirrors() + .stream() + .map(annotationValueInfoFactory::processAnnotation) + .collect(Collectors.toList()); + + methodInfo.setAnnotations(annotations); + + return Optional.of(methodInfo); + } + + private List getCompanionMethodParams(TypeElement companion, List modelMethods, + ExecutableElement methodElt, + Map descs, + boolean allowAnyJavaType) { + ExecutableType methodType = (ExecutableType) typeUtils.asMemberOf((DeclaredType) companion.asType(), methodElt); + ExecutableType methodType2 = (ExecutableType) methodElt.asType(); + List params = methodElt.getParameters(); + List mParams = new ArrayList<>(); + for (int i = 0; i < params.size(); i++) { + VariableElement param = params.get(i); + TypeMirror type = methodType.getParameterTypes().get(i); + TypeInfo typeInfo; + TypeUse typeUse = TypeUse.createParamTypeUse(env, modelMethods.toArray(new ExecutableElement[0]), i); + try { + typeInfo = typeFactory.create(typeUse, type); + } catch (Exception e) { + GenException ex = new GenException(param, e.getMessage()); + ex.setStackTrace(e.getStackTrace()); + throw ex; + } + if (!paramTypeChecker.check(methodElt, typeInfo, allowAnyJavaType)) { + throw new GenException(methodElt, "type " + typeInfo + " is not legal for use for a parameter in code generation"); + } + String name = param.getSimpleName().toString(); + String desc = descs.get(name); + Text text = desc != null ? new Text(desc).map(Token.tagMapper(elementUtils, typeUtils, companion)) : null; + TypeInfo unresolvedTypeInfo; + try { + unresolvedTypeInfo = typeFactory.create(typeUse, methodType2.getParameterTypes().get(i)); + } catch (Exception e) { + throw new GenException(param, e.getMessage()); + } + ParamInfo mParam = new ParamInfo(i, name, text, typeInfo, unresolvedTypeInfo); + mParams.add(mParam); + } + return mParams; + } + + // This is a hook to allow a specific type of method to be created + protected MethodInfo createMethodInfo(Set ownerTypes, String methodName, String comment, Doc doc, TypeInfo returnType, + Text returnDescription, + boolean isFluent, boolean isCacheReturn, List mParams, + ExecutableElement methodElt, boolean isStatic, boolean isDefault, ArrayList typeParams, + TypeElement declaringElt, boolean methodDeprecated, Text methodDeprecatedDesc) { + return new MethodInfo(ownerTypes, methodName, returnType, returnDescription, + isFluent, isCacheReturn, mParams, comment, doc, isStatic, isDefault, typeParams, methodDeprecated, methodDeprecatedDesc); + } +} diff --git a/src/main/java/io/vertx/codegen/util/DefaultMethodCreator.java b/src/main/java/io/vertx/codegen/util/DefaultMethodCreator.java new file mode 100644 index 000000000..5b84a98cb --- /dev/null +++ b/src/main/java/io/vertx/codegen/util/DefaultMethodCreator.java @@ -0,0 +1,321 @@ +package io.vertx.codegen.util; + +import io.vertx.codegen.GenException; +import io.vertx.codegen.Helper; +import io.vertx.codegen.MethodInfo; +import io.vertx.codegen.ParamInfo; +import io.vertx.codegen.TypeParamInfo; +import io.vertx.codegen.annotations.CacheReturn; +import io.vertx.codegen.annotations.Fluent; +import io.vertx.codegen.annotations.VertxGen; +import io.vertx.codegen.doc.Doc; +import io.vertx.codegen.doc.Tag; +import io.vertx.codegen.doc.Text; +import io.vertx.codegen.doc.Token; +import io.vertx.codegen.service.method.checker.Checker; +import io.vertx.codegen.service.method.checker.ParamTypeChecker; +import io.vertx.codegen.service.method.checker.ReturnTypeChecker; +import io.vertx.codegen.type.AnnotationValueInfo; +import io.vertx.codegen.type.AnnotationValueInfoFactory; +import io.vertx.codegen.type.ClassKind; +import io.vertx.codegen.type.ClassTypeInfo; +import io.vertx.codegen.type.TypeInfo; +import io.vertx.codegen.type.TypeMirrorFactory; +import io.vertx.codegen.type.TypeUse; + +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static io.vertx.codegen.util.ModelUtils.isObjectBound; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public class DefaultMethodCreator implements MethodCreator { + + private static final Logger logger = Logger.getLogger(DefaultMethodCreator.class.getName()); + + protected final ProcessingEnvironment env; + protected final AnnotationValueInfoFactory annotationValueInfoFactory; + protected final Messager messager; + protected final TypeMirrorFactory typeFactory; + protected final Elements elementUtils; + protected final Types typeUtils; + protected boolean deprecated; + protected final boolean concrete; + + protected final Checker returnTypeChecker; + protected final Checker paramTypeChecker; + + public DefaultMethodCreator(ProcessingEnvironment env, boolean concrete) { + this.elementUtils = env.getElementUtils(); + this.typeUtils = env.getTypeUtils(); + this.env = env; + this.concrete = concrete; + this.typeFactory = new TypeMirrorFactory(elementUtils, typeUtils); + this.messager = env.getMessager(); + this.annotationValueInfoFactory = new AnnotationValueInfoFactory(typeFactory); + this.returnTypeChecker = ReturnTypeChecker.getInstance(elementUtils); + this.paramTypeChecker = ParamTypeChecker.getInstance(elementUtils); + + } + + @Override + public Optional createMethod( + TypeElement modelElt, + ExecutableElement modelMethod, + boolean allowAnyJavaType, + Set collectedTypes, + Text deprecatedDesc + ) { + Doc.Factory docFactory = new Doc.Factory(env.getMessager(), elementUtils, typeUtils, typeFactory, modelElt); + + Set mods = modelMethod.getModifiers(); + if (!mods.contains(Modifier.PUBLIC)) { + return Optional.empty(); + } + + TypeElement declaringElt = (TypeElement) modelMethod.getEnclosingElement(); + TypeInfo declaringType = typeFactory.create(declaringElt.asType()); + + if (!declaringElt.equals(modelElt) && (declaringType.getKind() != ClassKind.API && declaringType.getKind() != ClassKind.HANDLER)) { + return Optional.empty(); + } + + ClassTypeInfo type = typeFactory.create(declaringElt.asType()).getRaw(); + + boolean isDefault = mods.contains(Modifier.DEFAULT); + boolean isStatic = mods.contains(Modifier.STATIC); + if (isStatic && !concrete) { + throw new GenException(modelMethod, "Abstract interface cannot declare static methods"); + } + + boolean isCacheReturn = modelMethod.getAnnotation(CacheReturn.class) != null; + ArrayList typeParams = new ArrayList<>(); + for (TypeParameterElement typeParam : modelMethod.getTypeParameters()) { + for (TypeMirror bound : typeParam.getBounds()) { + if (!isObjectBound(bound)) { + throw new GenException(modelMethod, "Type parameter bound not supported " + bound); + } + } + typeParams.add((TypeParamInfo.Method) TypeParamInfo.create(typeParam)); + } + + // + List modelMethods = new ArrayList<>(); + modelMethods.add(modelMethod); + + // Owner types + Set ownerTypes = new HashSet<>(); + ownerTypes.add(type); + + ArrayList ancestors = new ArrayList<>(Helper.resolveAncestorTypes(modelElt, true, true)); + + // Sort to have super types the last, etc.. + // solve some problem with diamond inheritance order that can show up in type use + ancestors.sort((o1, o2) -> { + if (typeUtils.isSubtype(o1, o2)) { + return -1; + } else if (typeUtils.isSubtype(o2, o1)) { + return 1; + } else { + return ((TypeElement) o1.asElement()).getQualifiedName().toString().compareTo(((TypeElement) o2.asElement()).getQualifiedName().toString()); + } + }); + + // Check overrides and merge type use + for (DeclaredType ancestorType : ancestors) { + TypeElement ancestorElt = (TypeElement) ancestorType.asElement(); + if (ancestorElt.getAnnotation(VertxGen.class) != null) { + elementUtils.getAllMembers(ancestorElt). + stream(). + flatMap(Helper.FILTER_METHOD). + filter(meth -> elementUtils.overrides(modelMethod, meth, modelElt)). + forEach(overridenMethodElt -> { + modelMethods.add(overridenMethodElt); + ownerTypes.add(typeFactory.create((DeclaredType) ancestorElt.asType()).getRaw()); + }); + } + } + + // + Map paramDescs = new HashMap<>(); + String comment = elementUtils.getDocComment(modelMethod); + Doc doc = docFactory.createDoc(modelMethod); + Text returnDesc = null; + Text methodDeprecatedDesc = null; + if (doc != null) { + doc. + getBlockTags(). + stream(). + filter(tag -> tag.getName().equals("param")). + map(Tag.Param::new). + forEach(tag -> paramDescs.put(tag.getParamName(), tag.getParamDescription())); + Optional returnTag = doc. + getBlockTags(). + stream(). + filter(tag -> tag.getName().equals("return")). + findFirst(); + if (returnTag.isPresent()) { + returnDesc = new Text(Helper.normalizeWhitespaces(returnTag.get().getValue())).map(Token.tagMapper(elementUtils, typeUtils, modelElt)); + } + Optional methodDeprecatedTag = doc. + getBlockTags(). + stream(). + filter(tag -> tag.getName().equals("deprecated")). + findFirst(); + if (methodDeprecatedTag.isPresent()) { + methodDeprecatedDesc = new Text(Helper.normalizeWhitespaces(methodDeprecatedTag.get().getValue())).map(Token.tagMapper(elementUtils, typeUtils, modelElt)); + } + } + + // + List mParams = getParams(modelElt, modelMethods, modelMethod, paramDescs, allowAnyJavaType); + + // + AnnotationMirror fluentAnnotation = Helper.resolveMethodAnnotation(Fluent.class, elementUtils, typeUtils, declaringElt, modelMethod); + boolean isFluent = fluentAnnotation != null; + if (isFluent) { + if (!typeUtils.isSameType(declaringElt.asType(), modelElt.asType())) { + String msg = "Interface " + modelElt + " does not redeclare the @Fluent return type " + + " of method " + modelMethod + " declared by " + declaringElt; + messager.printMessage(Diagnostic.Kind.WARNING, msg, modelElt, fluentAnnotation); + logger.warning(msg); + } else { + TypeMirror fluentType = modelMethod.getReturnType(); + if (!typeUtils.isAssignable(fluentType, modelElt.asType())) { + throw new GenException(modelMethod, "Methods marked with @Fluent must have a return type that extends the type"); + } + } + } + + // + TypeUse returnTypeUse = TypeUse.createReturnTypeUse(env, modelMethods.toArray(new ExecutableElement[0])); + + ExecutableType methodType = (ExecutableType) typeUtils.asMemberOf((DeclaredType) modelElt.asType(), modelMethod); + TypeInfo returnType; + try { + returnType = typeFactory.create(returnTypeUse, methodType.getReturnType()); + } catch (Exception e) { + GenException genEx = new GenException(modelMethod, e.getMessage()); + genEx.initCause(e); + throw genEx; + } + returnType.collectImports(collectedTypes); + if (isCacheReturn && returnType.isVoid()) { + throw new GenException(modelMethod, "void method can't be marked with @CacheReturn"); + } + String methodName = modelMethod.getSimpleName().toString(); + + // Only check the return type if not fluent, because generated code won't look it at anyway + if (!isFluent) { + if (!returnTypeChecker.check(modelMethod, returnType, allowAnyJavaType)) { + throw new GenException(modelMethod, "type " + returnType + " is not legal for use for a return type in code generation"); + } + } else if (returnType.isNullable()) { + throw new GenException(modelMethod, "Fluent return type cannot be nullable"); + } + + boolean methodDeprecated = modelMethod.getAnnotation(Deprecated.class) != null || deprecatedDesc != null; + + MethodInfo methodInfo = createMethodInfo( + ownerTypes, + methodName, + comment, + doc, + returnType, + returnDesc, + isFluent, + isCacheReturn, + mParams, + modelMethod, + isStatic, + isDefault, + typeParams, + declaringElt, + methodDeprecated, + methodDeprecatedDesc); + + List annotations = modelMethod.getAnnotationMirrors() + .stream() + .map(annotationValueInfoFactory::processAnnotation) + .collect(Collectors.toList()); + + methodInfo.setAnnotations(annotations); + + return Optional.of(methodInfo); + } + + // This is a hook to allow a specific type of method to be created + protected MethodInfo createMethodInfo(Set ownerTypes, String methodName, String comment, Doc doc, TypeInfo returnType, + Text returnDescription, + boolean isFluent, boolean isCacheReturn, List mParams, + ExecutableElement methodElt, boolean isStatic, boolean isDefault, ArrayList typeParams, + TypeElement declaringElt, boolean methodDeprecated, Text methodDeprecatedDesc) { + return new MethodInfo(ownerTypes, methodName, returnType, returnDescription, + isFluent, isCacheReturn, mParams, comment, doc, isStatic, isDefault, typeParams, methodDeprecated, methodDeprecatedDesc); + } + + + private List getParams(TypeElement elem, + List modelMethods, + ExecutableElement methodElt, + Map descs, + boolean allowAnyJavaType) { + ExecutableType methodType = (ExecutableType) typeUtils.asMemberOf((DeclaredType) elem.asType(), methodElt); + ExecutableType methodType2 = (ExecutableType) methodElt.asType(); + List params = methodElt.getParameters(); + List mParams = new ArrayList<>(); + for (int i = 0; i < params.size(); i++) { + VariableElement param = params.get(i); + TypeMirror type = methodType.getParameterTypes().get(i); + TypeInfo typeInfo; + TypeUse typeUse = TypeUse.createParamTypeUse(env, modelMethods.toArray(new ExecutableElement[0]), i); + try { + typeInfo = typeFactory.create(typeUse, type); + } catch (Exception e) { + GenException ex = new GenException(param, e.getMessage()); + ex.setStackTrace(e.getStackTrace()); + throw ex; + } + if (!paramTypeChecker.check(methodElt, typeInfo, allowAnyJavaType)) { + throw new GenException(methodElt, "type " + typeInfo + " is not legal for use for a parameter in code generation"); + } + String name = param.getSimpleName().toString(); + String desc = descs.get(name); + Text text = desc != null ? new Text(desc).map(Token.tagMapper(elementUtils, typeUtils, elem)) : null; + TypeInfo unresolvedTypeInfo; + try { + unresolvedTypeInfo = typeFactory.create(typeUse, methodType2.getParameterTypes().get(i)); + } catch (Exception e) { + throw new GenException(param, e.getMessage()); + } + ParamInfo mParam = new ParamInfo(i, name, text, typeInfo, unresolvedTypeInfo); + mParams.add(mParam); + } + return mParams; + } + + +} diff --git a/src/main/java/io/vertx/codegen/util/MethodCreator.java b/src/main/java/io/vertx/codegen/util/MethodCreator.java new file mode 100644 index 000000000..9e8b19420 --- /dev/null +++ b/src/main/java/io/vertx/codegen/util/MethodCreator.java @@ -0,0 +1,24 @@ +package io.vertx.codegen.util; + +import io.vertx.codegen.MethodInfo; +import io.vertx.codegen.doc.Text; +import io.vertx.codegen.type.ClassTypeInfo; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import java.util.Optional; +import java.util.Set; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public interface MethodCreator { + + Optional createMethod( + TypeElement modelElt, + ExecutableElement modelMethod, + boolean allowAnyJavaType, + Set collectedTypes, + Text deprecatedDesc + ); +} diff --git a/src/main/java/io/vertx/codegen/util/ModelUtils.java b/src/main/java/io/vertx/codegen/util/ModelUtils.java new file mode 100644 index 000000000..1cf405341 --- /dev/null +++ b/src/main/java/io/vertx/codegen/util/ModelUtils.java @@ -0,0 +1,33 @@ +package io.vertx.codegen.util; + +import io.vertx.codegen.type.ParameterizedTypeInfo; +import io.vertx.codegen.type.TypeInfo; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +/** + * @author Евгений Уткин (evgeny.utkin@mediascope.net) + */ +public abstract class ModelUtils { + + private ModelUtils() { + } + + public static boolean isObjectBound(TypeMirror bound) { + return bound.getKind() == TypeKind.DECLARED && bound.toString().equals(Object.class.getName()); + } + + public static boolean rawTypeIs(TypeInfo type, Class... classes) { + if (type instanceof ParameterizedTypeInfo) { + String rawClassName = type.getRaw().getName(); + for (Class c : classes) { + if (rawClassName.equals(c.getName())) { + return true; + } + } + } + + return false; + } +} diff --git a/src/test/java/io/vertx/test/codegen/ClassTestBase.java b/src/test/java/io/vertx/test/codegen/ClassTestBase.java index 97b79901f..5e0e87384 100644 --- a/src/test/java/io/vertx/test/codegen/ClassTestBase.java +++ b/src/test/java/io/vertx/test/codegen/ClassTestBase.java @@ -111,6 +111,7 @@ void assertGenInvalid(Class c, Class... rest) throws Exception { new GeneratorHelper().generateClass(c, rest); fail("Should throw exception"); } catch (GenException e) { + int i = 0; // OK } }