From 1375791008f3064a48ef5819f4baa04b1c99a177 Mon Sep 17 00:00:00 2001 From: eutkin Date: Fri, 26 Oct 2018 13:07:52 +0300 Subject: [PATCH 1/6] add companion --- .../java/io/vertx/codegen/ClassModel.java | 252 +++++++++++++++++- .../io/vertx/codegen/CompanionHelper.java | 55 ++++ .../java/io/vertx/codegen/MethodInfo.java | 22 +- 3 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/vertx/codegen/CompanionHelper.java diff --git a/src/main/java/io/vertx/codegen/ClassModel.java b/src/main/java/io/vertx/codegen/ClassModel.java index c164d255a..dbd676e74 100644 --- a/src/main/java/io/vertx/codegen/ClassModel.java +++ b/src/main/java/io/vertx/codegen/ClassModel.java @@ -677,6 +677,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)) { @@ -685,16 +687,43 @@ 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 -> { + elementUtils.getAllMembers(companion) + .stream() + .filter(elt -> !typeUtils.isSameType(elt.getEnclosingElement().asType(), objectType)) + .flatMap(Helper.FILTER_METHOD) + .filter(elt -> !isGenIgnore(elt)) + .forEach(elt -> { + boolean allowAnyJavaType = Helper.allowAnyJavaType(elt); + MethodInfo meth = createCompanionMethod(companion, elt, allowAnyJavaType); + if (meth != null) { + meth.collectImports(collectedTypes); + if (meth.isContainingAnyJavaType()) { +// anyJavaTypeMethods.put(elt, meth); + } else { +// methods.put(elt, meth); + } + } + }); + }); + // Traverse fields elementUtils.getAllMembers((TypeElement) elem).stream(). filter(elt -> !typeUtils.isSameType(elt.getEnclosingElement().asType(), objectType)). @@ -702,7 +731,8 @@ private void traverseType(Element elem) { filter(elt -> !isGenIgnore(elt)). forEach(elt -> { boolean allowAnyJavaType = Helper.allowAnyJavaType(elt); - ConstantInfo cst = fieldMethod(elt, allowAnyJavaType); + boolean isCompanionField = companionOpt.filter(companion -> elt.asType() == companion.asType()).isPresent(); + ConstantInfo cst = fieldMethod(elt, allowAnyJavaType || isCompanionField); if (cst != null) { cst.getType().collectImports(collectedTypes); constants.add(cst); @@ -767,6 +797,190 @@ private ConstantInfo fieldMethod(VariableElement modelField, boolean allowAnyJav return new ConstantInfo(doc, modelField.getSimpleName().toString(), type); } + private MethodInfo createCompanionMethod(TypeElement companion, ExecutableElement modelMethod, boolean allowAnyJavaType) { + Set mods = modelMethod.getModifiers(); + if (!mods.contains(Modifier.PUBLIC)) { + return null; + } + + 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 + 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, 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, 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 = getCompanionMethodParams(companion, modelMethods, modelMethod, paramDescs, allowAnyJavaType); + + // + AnnotationMirror fluentAnnotation = Helper.resolveMethodAnnotation(Fluent.class, elementUtils, typeUtils, companion, modelMethod); + boolean isFluent = fluentAnnotation != null; + if (isFluent) { + isFluent = true; + 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[modelMethods.size()])); + + 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 (!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, + false, + typeParams, + companion, + methodDeprecated, + methodDeprecatedDesc).setCompanion(companion.getSimpleName()); + + // 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 (!methodInfo.isContainingAnyJavaType()) { + 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); + } + + return methodInfo; + } + private MethodInfo createMethod(ExecutableElement modelMethod, boolean allowAnyJavaType) { Set mods = modelMethod.getModifiers(); if (!mods.contains(Modifier.PUBLIC)) { @@ -1000,6 +1214,42 @@ private boolean isObjectBound(TypeMirror bound) { return bound.getKind() == TypeKind.DECLARED && bound.toString().equals(Object.class.getName()); } + 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[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, 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; + } + private List getParams(List modelMethods, ExecutableElement methodElt, Map descs, 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..89c868210 100644 --- a/src/main/java/io/vertx/codegen/MethodInfo.java +++ b/src/main/java/io/vertx/codegen/MethodInfo.java @@ -24,7 +24,13 @@ 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.Set; /** * @author Tim Fox @@ -45,6 +51,7 @@ public class MethodInfo implements Comparable { private List params; private boolean deprecated; private Text deprecatedDesc; + private Name companionObjectName; public MethodInfo(Set ownerTypes, String name, TypeInfo returnType, Text returnDescription, boolean fluent, boolean cacheReturn, @@ -285,6 +292,19 @@ public MethodInfo setDefaultMethod(boolean defaultMethod) { return this; } + public Name getCompanionObjectName() { + return companionObjectName; + } + + public MethodInfo setCompanion(Name companionObjectName) { + this.companionObjectName = companionObjectName; + return this; + } + + public boolean isCompanionMethod() { + return companionObjectName != null; + } + /** * * @return {@code true} if the method has a {@code @Deprecated} annotation From 4af42273a72cb9527f0da68275ab9d1523148a11 Mon Sep 17 00:00:00 2001 From: eutkin Date: Fri, 26 Oct 2018 15:15:41 +0300 Subject: [PATCH 2/6] add companion object of Kotlin --- src/main/java/io/vertx/codegen/ClassModel.java | 4 ++-- src/main/java/io/vertx/codegen/MethodInfo.java | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/vertx/codegen/ClassModel.java b/src/main/java/io/vertx/codegen/ClassModel.java index dbd676e74..69f030216 100644 --- a/src/main/java/io/vertx/codegen/ClassModel.java +++ b/src/main/java/io/vertx/codegen/ClassModel.java @@ -716,9 +716,9 @@ private void traverseType(Element elem) { if (meth != null) { meth.collectImports(collectedTypes); if (meth.isContainingAnyJavaType()) { -// anyJavaTypeMethods.put(elt, meth); + anyJavaTypeMethods.put(elt, meth); } else { -// methods.put(elt, meth); + methods.put(elt, meth); } } }); diff --git a/src/main/java/io/vertx/codegen/MethodInfo.java b/src/main/java/io/vertx/codegen/MethodInfo.java index 89c868210..a67f3e430 100644 --- a/src/main/java/io/vertx/codegen/MethodInfo.java +++ b/src/main/java/io/vertx/codegen/MethodInfo.java @@ -30,6 +30,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -51,7 +52,7 @@ public class MethodInfo implements Comparable { private List params; private boolean deprecated; private Text deprecatedDesc; - private Name companionObjectName; + private Name companion; public MethodInfo(Set ownerTypes, String name, TypeInfo returnType, Text returnDescription, boolean fluent, boolean cacheReturn, @@ -292,17 +293,17 @@ public MethodInfo setDefaultMethod(boolean defaultMethod) { return this; } - public Name getCompanionObjectName() { - return companionObjectName; + public String getCompanion() { + return Objects.toString(companion); } public MethodInfo setCompanion(Name companionObjectName) { - this.companionObjectName = companionObjectName; + this.companion = companionObjectName; return this; } public boolean isCompanionMethod() { - return companionObjectName != null; + return companion != null; } /** From d3cd2a5d731787bdc9e002040fc2feb70ec122b9 Mon Sep 17 00:00:00 2001 From: eutkin Date: Wed, 7 Nov 2018 17:32:37 +0300 Subject: [PATCH 3/6] fix test for window machine --- .../io/vertx/codegen/CodeGenProcessor.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/vertx/codegen/CodeGenProcessor.java b/src/main/java/io/vertx/codegen/CodeGenProcessor.java index 9d5c0dac9..ebcf02aeb 100644 --- a/src/main/java/io/vertx/codegen/CodeGenProcessor.java +++ b/src/main/java/io/vertx/codegen/CodeGenProcessor.java @@ -21,7 +21,17 @@ import java.io.File; import java.io.FileWriter; import java.io.Writer; -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; @@ -243,11 +253,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 { From df9702878620c7585b4d5ad025aa6ffe9dd28392 Mon Sep 17 00:00:00 2001 From: eutkin Date: Mon, 12 Nov 2018 11:04:06 +0300 Subject: [PATCH 4/6] refactoring class model --- .../java/io/vertx/codegen/ClassModel.java | 442 ++++++------------ .../java/io/vertx/codegen/MethodInfo.java | 11 + ...ParameterizedVertxGenInterfaceChecker.java | 41 ++ .../service/method/checker/Checker.java | 15 + .../LegalCallbackValueTypeChecker.java | 35 ++ .../checker/LegalClassTypeParamChecker.java | 36 ++ .../checker/LegalContainerParamChecker.java | 57 +++ .../checker/LegalContainerReturnChecker.java | 63 +++ .../LegalDataObjectTypeParamChecker.java | 26 ++ .../LegalDataObjectTypeReturnChecker.java | 46 ++ .../method/checker/LegalEnumChecker.java | 21 + .../checker/LegalFunctionTypeChecker.java | 43 ++ .../LegalHandlerAsyncResultTypeChecker.java | 40 ++ .../checker/LegalHandlerTypeChecker.java | 37 ++ .../checker/LegalNonCallableParamChecker.java | 45 ++ .../LegalNonCallableReturnTypeChecker.java | 46 ++ ...ParameterizedVertxGenInterfaceChecker.java | 24 + .../method/checker/ParamTypeChecker.java | 40 ++ .../method/checker/ReturnTypeChecker.java | 39 ++ .../method/checker/TypeVariableChecker.java | 21 + .../codegen/util/CompanionMethodCreator.java | 21 + .../codegen/util/DefaultMethodCreator.java | 321 +++++++++++++ .../io/vertx/codegen/util/MethodCreator.java | 24 + .../io/vertx/codegen/util/ModelUtils.java | 33 ++ .../io/vertx/test/codegen/ClassTestBase.java | 1 + 25 files changed, 1230 insertions(+), 298 deletions(-) create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/AllowParameterizedVertxGenInterfaceChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/Checker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalCallbackValueTypeChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalClassTypeParamChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalContainerParamChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalContainerReturnChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalDataObjectTypeParamChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalDataObjectTypeReturnChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalEnumChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalFunctionTypeChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalHandlerAsyncResultTypeChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalHandlerTypeChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalNonCallableParamChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalNonCallableReturnTypeChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/NotAllowParameterizedVertxGenInterfaceChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/ParamTypeChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/ReturnTypeChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/TypeVariableChecker.java create mode 100644 src/main/java/io/vertx/codegen/util/CompanionMethodCreator.java create mode 100644 src/main/java/io/vertx/codegen/util/DefaultMethodCreator.java create mode 100644 src/main/java/io/vertx/codegen/util/MethodCreator.java create mode 100644 src/main/java/io/vertx/codegen/util/ModelUtils.java diff --git a/src/main/java/io/vertx/codegen/ClassModel.java b/src/main/java/io/vertx/codegen/ClassModel.java index 69f030216..3e39113ef 100644 --- a/src/main/java/io/vertx/codegen/ClassModel.java +++ b/src/main/java/io/vertx/codegen/ClassModel.java @@ -25,16 +25,49 @@ 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.TypeUse; +import io.vertx.codegen.type.TypeVariableInfo; +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.AnnotationMirror; +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.TypeParameterElement; +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.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +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.logging.Logger; import java.util.stream.Collectors; @@ -89,6 +122,8 @@ public class ClassModel implements Model { protected boolean deprecated; protected Text deprecatedDesc; + protected MethodCreator defaultMethodCreator; + public ClassModel(ProcessingEnvironment env, TypeElement modelElt) { this.elementUtils = env.getElementUtils(); this.typeUtils = env.getTypeUtils(); @@ -221,7 +256,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,7 +286,7 @@ 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())); } } @@ -409,13 +444,13 @@ protected boolean isLegalDataObjectTypeReturn(TypeInfo type) { 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(); } } @@ -462,11 +497,11 @@ protected boolean isLegalContainerReturn(TypeInfo type, boolean allowAnyJavaType } else { TypeInfo valueType = args.get(0); if (valueType.getKind().basic || - valueType.getKind().json || - valueType.getKind() == ClassKind.ENUM || - isVertxGenInterface(valueType, false) || - (allowAnyJavaType && valueType.getKind() == ClassKind.OTHER )|| - isLegalDataObjectTypeReturn(valueType)) { + valueType.getKind().json || + valueType.getKind() == ClassKind.ENUM || + isVertxGenInterface(valueType, false) || + (allowAnyJavaType && valueType.getKind() == ClassKind.OTHER) || + isLegalDataObjectTypeReturn(valueType)) { return true; } } @@ -482,7 +517,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()) { @@ -543,22 +578,22 @@ private boolean isLegalCallbackValueType(TypeInfo type, boolean 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(ClassTypeInfo.class)). - filter(t -> t.getKind() == ClassKind.DATA_OBJECT). - collect(Collectors.toSet()); + map(ClassTypeInfo::getRaw). + flatMap(Helper.instanceOf(ClassTypeInfo.class)). + filter(t -> t.getKind() == ClassKind.DATA_OBJECT). + collect(Collectors.toSet()); referencedEnumTypes = collectedTypes.stream(). map(ClassTypeInfo::getRaw). @@ -610,6 +645,9 @@ 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); + DeclaredType tm = (DeclaredType) elem.asType(); List typeArgs = tm.getTypeArguments(); for (TypeMirror typeArg : typeArgs) { @@ -619,7 +657,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 { @@ -654,18 +692,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; @@ -699,7 +738,6 @@ private void traverseType(Element elem) { } - if (elem.getKind() == ElementKind.INTERFACE) { TypeMirror objectType = elementUtils.getTypeElement("java.lang.Object").asType(); @@ -742,27 +780,75 @@ private void traverseType(Element elem) { // Traverse methods elementUtils.getAllMembers((TypeElement) elem).stream(). - filter(elt -> !typeUtils.isSameType(elt.getEnclosingElement().asType(), objectType)). - flatMap(Helper.FILTER_METHOD). - filter(elt -> !isGenIgnore(elt)). - forEach(elt -> { - boolean allowAnyJavaType = Helper.allowAnyJavaType(elt); - MethodInfo meth = createMethod(elt, allowAnyJavaType); - if (meth != null) { - meth.collectImports(collectedTypes); - if (meth.isContainingAnyJavaType()) { - anyJavaTypeMethods.put(elt, meth); - } else { - methods.put(elt, meth); + filter(elt -> !typeUtils.isSameType(elt.getEnclosingElement().asType(), objectType)). + flatMap(Helper.FILTER_METHOD). + filter(elt -> !isGenIgnore(elt)). + forEach(elt -> { + boolean allowAnyJavaType = Helper.allowAnyJavaType(elt); + Optional methodInfoOpt = defaultMethodCreator.createMethod(modelElt, 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); + } + }); + + }); // Sort method map sortMethodMap(methodMap); // Now check for overloaded methods - for (List meths: methodMap.values()) { + for (List meths : methodMap.values()) { // Ambiguous try { @@ -792,7 +878,7 @@ 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); } @@ -908,7 +994,7 @@ private MethodInfo createCompanionMethod(TypeElement companion, ExecutableElemen } // - TypeUse returnTypeUse = TypeUse.createReturnTypeUse(env, modelMethods.toArray(new ExecutableElement[modelMethods.size()])); + TypeUse returnTypeUse = TypeUse.createReturnTypeUse(env, modelMethods.toArray(new ExecutableElement[modelMethods.size()])); ExecutableType methodType = (ExecutableType) typeUtils.asMemberOf((DeclaredType) companion.asType(), modelMethod); TypeInfo returnType; @@ -981,211 +1067,6 @@ private MethodInfo createCompanionMethod(TypeElement companion, ExecutableElemen return methodInfo; } - 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 (!methodInfo.isContainingAnyJavaType()) { - 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, @@ -1201,7 +1082,7 @@ 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) { + 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()); @@ -1215,14 +1096,14 @@ private boolean isObjectBound(TypeMirror bound) { } private List getCompanionMethodParams(TypeElement companion, List modelMethods, - ExecutableElement methodElt, - Map descs, - boolean allowAnyJavaType) { + 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++) { + for (int i = 0; i < params.size(); i++) { VariableElement param = params.get(i); TypeMirror type = methodType.getParameterTypes().get(i); TypeInfo typeInfo; @@ -1250,48 +1131,13 @@ private List getCompanionMethodParams(TypeElement companion, 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/MethodInfo.java b/src/main/java/io/vertx/codegen/MethodInfo.java index a67f3e430..4c56e475e 100644 --- a/src/main/java/io/vertx/codegen/MethodInfo.java +++ b/src/main/java/io/vertx/codegen/MethodInfo.java @@ -18,6 +18,7 @@ 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; @@ -53,6 +54,7 @@ public class MethodInfo implements Comparable { 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, @@ -340,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/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..178440cab --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerParamChecker.java @@ -0,0 +1,57 @@ +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 vertxGenInterfaceChecker; + private final Checker legalDataObjectTypeParamChecker; + + public static Checker getInstance() { + return new LegalContainerParamChecker(); + } + + public LegalContainerParamChecker(Checker vertxGenInterfaceChecker, Checker legalDataObjectTypeParamChecker) { + this.vertxGenInterfaceChecker = vertxGenInterfaceChecker; + this.legalDataObjectTypeParamChecker = legalDataObjectTypeParamChecker; + } + + + public LegalContainerParamChecker() { + this.vertxGenInterfaceChecker = NotAllowParameterizedVertxGenInterfaceChecker.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) { + return argument.getKind().basic || + argument.getKind().json || + vertxGenInterfaceChecker.check(elt, argument, false) || + legalDataObjectTypeParamChecker.check(elt, argument, false) || + argument.getKind() == ClassKind.ENUM || + (allowAnyJavaType && argument.getKind() == ClassKind.OTHER); + } else if (argument.getKind() == ClassKind.STRING) { // Only allow Map's with String's for keys + argument = ((ParameterizedTypeInfo) type).getArgs().get(1); + return argument.getKind().basic || argument.getKind().json || vertxGenInterfaceChecker.check(elt, argument, false) || (allowAnyJavaType && argument.getKind() == ClassKind.OTHER); + } + } + 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..214f6a884 --- /dev/null +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerReturnChecker.java @@ -0,0 +1,63 @@ +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; + + public static Checker getInstance(Elements elementsUtils) { + return new LegalContainerReturnChecker(elementsUtils); + } + + public LegalContainerReturnChecker(Checker vertxGenInterfaceChecker, Checker legalDataObjectTypeReturnChecker) { + this.vertxGenInterfaceChecker = vertxGenInterfaceChecker; + this.legalDataObjectTypeReturnChecker = legalDataObjectTypeReturnChecker; + } + + + + public LegalContainerReturnChecker(Elements elementsUtils) { + this.vertxGenInterfaceChecker = NotAllowParameterizedVertxGenInterfaceChecker.getInstance(); + this.legalDataObjectTypeReturnChecker = LegalDataObjectTypeReturnChecker.getInstance(elementsUtils); + } + + @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 valueType.getKind().basic || + valueType.getKind().json || + (allowAnyJavaType && valueType.getKind() == ClassKind.OTHER); + } else { + TypeInfo valueType = args.get(0); + return valueType.getKind().basic || + valueType.getKind().json || + valueType.getKind() == ClassKind.ENUM || + vertxGenInterfaceChecker.check(elt, valueType, allowAnyJavaType) || + (allowAnyJavaType && valueType.getKind() == ClassKind.OTHER) || + legalDataObjectTypeReturnChecker.check(elt, valueType, allowAnyJavaType); + } + } + 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..ac7314f13 --- /dev/null +++ b/src/main/java/io/vertx/codegen/util/CompanionMethodCreator.java @@ -0,0 +1,21 @@ +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 class CompanionMethodCreator implements MethodCreator { + + @Override + public Optional createMethod(TypeElement modelElt, ExecutableElement modelMethod, boolean allowAnyJavaType, Set collectedTypes, Text deprecatedDesc) { + return Optional.empty(); + } +} 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 } } From 7b491a0cc393d4a3b4c304768c511f48c66e786f Mon Sep 17 00:00:00 2001 From: eutkin Date: Wed, 5 Dec 2018 13:10:59 +0300 Subject: [PATCH 5/6] refactoring class model --- .../java/io/vertx/codegen/ClassModel.java | 382 +++--------------- .../codegen/util/CompanionMethodCreator.java | 279 ++++++++++++- 2 files changed, 335 insertions(+), 326 deletions(-) diff --git a/src/main/java/io/vertx/codegen/ClassModel.java b/src/main/java/io/vertx/codegen/ClassModel.java index 3e39113ef..5f1ce1225 100644 --- a/src/main/java/io/vertx/codegen/ClassModel.java +++ b/src/main/java/io/vertx/codegen/ClassModel.java @@ -16,12 +16,9 @@ * 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; @@ -35,20 +32,18 @@ import io.vertx.codegen.type.ParameterizedTypeInfo; import io.vertx.codegen.type.TypeInfo; import io.vertx.codegen.type.TypeMirrorFactory; -import io.vertx.codegen.type.TypeUse; 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.AnnotationMirror; 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.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; @@ -57,18 +52,14 @@ 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.ArrayList; -import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; 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.logging.Logger; import java.util.stream.Collectors; /** @@ -85,7 +76,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; @@ -123,6 +113,7 @@ public class ClassModel implements Model { protected Text deprecatedDesc; protected MethodCreator defaultMethodCreator; + protected MethodCreator companionMethodCreator; public ClassModel(ProcessingEnvironment env, TypeElement modelElt) { this.elementUtils = env.getElementUtils(); @@ -291,41 +282,6 @@ private void sortMethodMap(Map> map) { } } - 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; @@ -423,22 +379,6 @@ private boolean isLegalDataObjectTypeParam(TypeInfo type) { 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()); @@ -535,17 +475,6 @@ 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); @@ -647,6 +576,7 @@ private void traverseType(Element elem) { 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(); @@ -750,15 +680,63 @@ private void traverseType(Element elem) { .filter(elt -> !isGenIgnore(elt)) .forEach(elt -> { boolean allowAnyJavaType = Helper.allowAnyJavaType(elt); - MethodInfo meth = createCompanionMethod(companion, elt, allowAnyJavaType); - if (meth != null) { - meth.collectImports(collectedTypes); - if (meth.isContainingAnyJavaType()) { - anyJavaTypeMethods.put(elt, meth); + Optional methodInfoOpt = companionMethodCreator.createMethod(companion, 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 { - methods.put(elt, meth); + 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); + } + }); + }); }); @@ -883,254 +861,10 @@ private ConstantInfo fieldMethod(VariableElement modelField, boolean allowAnyJav return new ConstantInfo(doc, modelField.getSimpleName().toString(), type); } - private MethodInfo createCompanionMethod(TypeElement companion, ExecutableElement modelMethod, boolean allowAnyJavaType) { - Set mods = modelMethod.getModifiers(); - if (!mods.contains(Modifier.PUBLIC)) { - return null; - } - - 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 - 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, 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, 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 = getCompanionMethodParams(companion, modelMethods, modelMethod, paramDescs, allowAnyJavaType); - - // - AnnotationMirror fluentAnnotation = Helper.resolveMethodAnnotation(Fluent.class, elementUtils, typeUtils, companion, modelMethod); - boolean isFluent = fluentAnnotation != null; - if (isFluent) { - isFluent = true; - 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[modelMethods.size()])); - - 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 (!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, - false, - typeParams, - companion, - methodDeprecated, - methodDeprecatedDesc).setCompanion(companion.getSimpleName()); - - // 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 (!methodInfo.isContainingAnyJavaType()) { - 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); - } - - 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 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[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, 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; - } - /** * @return {@code true} if the class has a {@code @Deprecated} annotation */ diff --git a/src/main/java/io/vertx/codegen/util/CompanionMethodCreator.java b/src/main/java/io/vertx/codegen/util/CompanionMethodCreator.java index ac7314f13..da0f92675 100644 --- a/src/main/java/io/vertx/codegen/util/CompanionMethodCreator.java +++ b/src/main/java/io/vertx/codegen/util/CompanionMethodCreator.java @@ -1,21 +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 modelElt, ExecutableElement modelMethod, boolean allowAnyJavaType, Set collectedTypes, Text deprecatedDesc) { - return Optional.empty(); + 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); } } From 11f4e7ef5e569793b3d93fec04a76df93002f9fa Mon Sep 17 00:00:00 2001 From: eutkin Date: Wed, 5 Dec 2018 15:03:43 +0300 Subject: [PATCH 6/6] refactoring class model --- .../java/io/vertx/codegen/ClassModel.java | 334 +++++------------- .../LegalArgumentContainerParamChecker.java | 36 ++ .../LegalArgumentContainerReturnChecker.java | 25 ++ .../checker/LegalContainerParamChecker.java | 21 +- .../checker/LegalContainerReturnChecker.java | 19 +- 5 files changed, 166 insertions(+), 269 deletions(-) create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalArgumentContainerParamChecker.java create mode 100644 src/main/java/io/vertx/codegen/service/method/checker/LegalArgumentContainerReturnChecker.java diff --git a/src/main/java/io/vertx/codegen/ClassModel.java b/src/main/java/io/vertx/codegen/ClassModel.java index 589441d49..fe0cd1e48 100644 --- a/src/main/java/io/vertx/codegen/ClassModel.java +++ b/src/main/java/io/vertx/codegen/ClassModel.java @@ -53,6 +53,7 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashMap; @@ -76,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; @@ -331,55 +331,10 @@ 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; - } - protected boolean isLegalDataObjectTypeReturn(TypeInfo type) { if (type.getKind() == ClassKind.DATA_OBJECT) { TypeElement typeElt = elementUtils.getTypeElement(type.getName()); @@ -398,34 +353,6 @@ protected boolean isLegalDataObjectTypeReturn(TypeInfo type) { 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(); @@ -480,36 +407,6 @@ private boolean isVertxGenInterface(TypeInfo type, boolean allowParameterized) { 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). @@ -524,10 +421,10 @@ private void determineApiTypes() { 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). @@ -678,71 +575,7 @@ private void traverseType(Element elem) { TypeMirror objectType = elementUtils.getTypeElement("java.lang.Object").asType(); companionOpt.ifPresent(companion -> { - elementUtils.getAllMembers(companion) - .stream() - .filter(elt -> !typeUtils.isSameType(elt.getEnclosingElement().asType(), objectType)) - .flatMap(Helper.FILTER_METHOD) - .filter(elt -> !isGenIgnore(elt)) - .forEach(elt -> { - boolean allowAnyJavaType = Helper.allowAnyJavaType(elt); - Optional methodInfoOpt = companionMethodCreator.createMethod(companion, 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); - } - }); - - }); + traverseMethods(companionMethodCreator, objectType, companion); }); // Traverse fields @@ -762,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); @@ -770,80 +603,7 @@ 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; - } - Optional methodInfoOpt = defaultMethodCreator.createMethod(modelElt, 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); - } - }); - - }); + traverseMethods(defaultMethodCreator, objectType, modelElt); // Sort method map sortMethodMap(methodMap); @@ -869,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; } @@ -894,6 +731,7 @@ private boolean isObjectBound(TypeMirror bound) { public boolean isDeprecated() { return deprecated; } + /** * @return the description of deprecated */ 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/LegalContainerParamChecker.java b/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerParamChecker.java index 178440cab..0fc2a6c78 100644 --- a/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerParamChecker.java +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerParamChecker.java @@ -16,21 +16,21 @@ */ public class LegalContainerParamChecker implements Checker { - private final Checker vertxGenInterfaceChecker; + private final Checker legalArgumentContainerParamChecker; private final Checker legalDataObjectTypeParamChecker; public static Checker getInstance() { return new LegalContainerParamChecker(); } - public LegalContainerParamChecker(Checker vertxGenInterfaceChecker, Checker legalDataObjectTypeParamChecker) { - this.vertxGenInterfaceChecker = vertxGenInterfaceChecker; + public LegalContainerParamChecker(Checker legalArgumentContainerParamChecker, Checker legalDataObjectTypeParamChecker) { + this.legalArgumentContainerParamChecker = legalArgumentContainerParamChecker; this.legalDataObjectTypeParamChecker = legalDataObjectTypeParamChecker; } public LegalContainerParamChecker() { - this.vertxGenInterfaceChecker = NotAllowParameterizedVertxGenInterfaceChecker.getInstance(); + this.legalArgumentContainerParamChecker = LegalArgumentContainerParamChecker.getInstance(); this.legalDataObjectTypeParamChecker = LegalDataObjectTypeParamChecker.getInstance(); } @@ -41,15 +41,14 @@ public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaT if (rawTypeIs(type, List.class, Set.class, Map.class)) { TypeInfo argument = ((ParameterizedTypeInfo) type).getArgs().get(0); if (type.getKind() != ClassKind.MAP) { - return argument.getKind().basic || - argument.getKind().json || - vertxGenInterfaceChecker.check(elt, argument, false) || + if (legalArgumentContainerParamChecker.check(elt, argument, allowAnyJavaType) || legalDataObjectTypeParamChecker.check(elt, argument, false) || - argument.getKind() == ClassKind.ENUM || - (allowAnyJavaType && argument.getKind() == ClassKind.OTHER); - } else if (argument.getKind() == ClassKind.STRING) { // Only allow Map's with String's for keys + argument.getKind() == ClassKind.ENUM) { + return true; + } + } else if (argument.getKind() == ClassKind.STRING) { argument = ((ParameterizedTypeInfo) type).getArgs().get(1); - return argument.getKind().basic || argument.getKind().json || vertxGenInterfaceChecker.check(elt, argument, false) || (allowAnyJavaType && argument.getKind() == ClassKind.OTHER); + 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 index 214f6a884..be4e8bf1b 100644 --- a/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerReturnChecker.java +++ b/src/main/java/io/vertx/codegen/service/method/checker/LegalContainerReturnChecker.java @@ -19,25 +19,28 @@ 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) { + 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) { @@ -45,17 +48,13 @@ public boolean check(ExecutableElement elt, TypeInfo type, boolean allowAnyJavaT return false; } TypeInfo valueType = args.get(1); - return valueType.getKind().basic || - valueType.getKind().json || - (allowAnyJavaType && valueType.getKind() == ClassKind.OTHER); + return legalArgumentContainerReturn.check(elt, valueType, allowAnyJavaType); } else { TypeInfo valueType = args.get(0); - return valueType.getKind().basic || - valueType.getKind().json || + return legalArgumentContainerReturn.check(elt, valueType, allowAnyJavaType) || valueType.getKind() == ClassKind.ENUM || - vertxGenInterfaceChecker.check(elt, valueType, allowAnyJavaType) || - (allowAnyJavaType && valueType.getKind() == ClassKind.OTHER) || - legalDataObjectTypeReturnChecker.check(elt, valueType, allowAnyJavaType); + vertxGenInterfaceChecker.check(elt, valueType, false) || + legalDataObjectTypeReturnChecker.check(elt, valueType, false); } } return false;