package io.vertx.codegen; /* * Copyright 2014 Red Hat, Inc. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ import io.vertx.codegen.annotations.CacheReturn; import io.vertx.codegen.annotations.Fluent; import io.vertx.codegen.annotations.GenIgnore; import io.vertx.codegen.annotations.VertxGen; import io.vertx.codegen.doc.Doc; import io.vertx.codegen.doc.Tag; import io.vertx.codegen.doc.Text; import io.vertx.codegen.doc.Token; import io.vertx.codegen.overloadcheck.MethodOverloadChecker; import io.vertx.codegen.type.*; import io.vertx.codegen.type.VoidTypeInfo; import javax.annotation.processing.Messager; 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.lang.reflect.AnnotatedType; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; 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; /** * A processed source. * * @author <a href="http://tfox.org">Tim Fox</a> */ public class ClassModel implements Model { private static final Logger logger = Logger.getLogger(ClassModel.class.getName()); public static final String VERTX_READ_STREAM = "io.vertx.core.streams.ReadStream"; public static final String VERTX_WRITE_STREAM = "io.vertx.core.streams.WriteStream"; public static final String VERTX_ASYNC_RESULT = "io.vertx.core.AsyncResult"; public static final String VERTX_HANDLER = "io.vertx.core.Handler"; 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"; protected final MethodOverloadChecker methodOverloadChecker; protected final Messager messager; protected final TypeMirrorFactory typeFactory; protected final Doc.Factory docFactory; protected final Map<String, TypeElement> sources; protected final TypeElement modelElt; protected final Elements elementUtils; protected final Types typeUtils; protected boolean processed = false; protected LinkedHashMap<ExecutableElement, MethodInfo> methods = new LinkedHashMap<>(); protected Set<ClassTypeInfo> collectedTypes = new HashSet<>(); protected Set<ClassTypeInfo> importedTypes = new HashSet<>(); protected Set<ApiTypeInfo> referencedTypes = new HashSet<>(); protected Set<ClassTypeInfo> referencedDataObjectTypes = new HashSet<>(); protected boolean concrete; protected ClassTypeInfo type; protected String ifaceSimpleName; protected String ifaceFQCN; protected String ifacePackageName; protected String ifaceComment; protected Doc doc; protected List<TypeInfo> superTypes = new ArrayList<>(); protected TypeInfo concreteSuperType; protected List<TypeInfo> abstractSuperTypes = new ArrayList<>(); // The methods, grouped by name protected Map<String, List<MethodInfo>> methodMap = new LinkedHashMap<>(); public ClassModel(MethodOverloadChecker methodOverloadChecker, Messager messager, Map<String, TypeElement> sources, Elements elementUtils, Types typeUtils, TypeElement modelElt) { this.methodOverloadChecker = methodOverloadChecker; this.typeFactory = new TypeMirrorFactory(elementUtils, typeUtils); this.docFactory = new Doc.Factory(messager, elementUtils, typeUtils, typeFactory, modelElt); this.messager = messager; this.sources = sources; this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.modelElt = modelElt; } @Override public String getKind() { return "class"; } @Override public String getFqn() { return type.getRaw().getName(); } public TypeElement getElement() { return modelElt; } public List<MethodInfo> getMethods() { return new ArrayList<>(methods.values()); } public List<MethodInfo> getStaticMethods() { return methods.values().stream().filter(MethodInfo::isStaticMethod).collect(Collectors.toList()); } public List<MethodInfo> getInstanceMethods() { return methods.values().stream().filter(m -> !m.isStaticMethod()).collect(Collectors.toList()); } public boolean isConcrete() { return concrete; } /** * @return all classes that are not in the same package */ public Set<ClassTypeInfo> getImportedTypes() { return importedTypes; } /** * @return all the referenced api types */ public Set<ApiTypeInfo> getReferencedTypes() { return referencedTypes; } /** * @return all the referenced data object types */ public Set<ClassTypeInfo> getReferencedDataObjectTypes() { return referencedDataObjectTypes; } public String getIfaceSimpleName() { return ifaceSimpleName; } public String getIfaceFQCN() { return ifaceFQCN; } public String getIfacePackageName() { return ifacePackageName; } public String getIfaceComment() { return ifaceComment; } public Doc getDoc() { return doc; } public ClassTypeInfo getType() { return type; } public ModuleInfo getModule() { return type.getRaw().getModule(); } public List<TypeInfo> getSuperTypes() { return superTypes; } public TypeInfo getConcreteSuperType() { return concreteSuperType; } public List<TypeInfo> getAbstractSuperTypes() { return abstractSuperTypes; } public TypeInfo getHandlerType() { return (type.getKind() == ClassKind.API) ? ((ApiTypeInfo)type).getHandlerArg() : null; } public Map<String, List<MethodInfo>> getMethodMap() { return methodMap; } public List<TypeParamInfo.Class> getTypeParams() { return type.getRaw().getParams(); } public List<TypeInfo> getSuperTypeArguments() { if (concreteSuperType != null && concreteSuperType.isParameterized()) { DeclaredType tm = (DeclaredType) modelElt.asType();; List<? extends TypeMirror> st = typeUtils.directSupertypes(tm); 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<TypeInfo> list = new ArrayList<>(); int size = tt.getTypeParameters().size(); for (int i = 0; i< size;i++) { TypeMirror q = abc.getTypeArguments().get(i); TypeInfo ti = typeFactory.create(q); list.add(ti); } return list; } } } } return null; } private void sortMethodMap(Map<String, List<MethodInfo>> map) { for (List<MethodInfo> list: map.values()) { list.sort((meth1, meth2) -> meth1.params.size() - meth2.params.size()); } } protected void checkParamType(ExecutableElement elem, TypeMirror type, TypeInfo typeInfo, int pos, int numParams) { if (isLegalNonCallableParam(typeInfo)) { return; } if (isLegalClassTypeParam(elem, typeInfo)) { return; } if (isLegalHandlerType(typeInfo)) { return; } if (isLegalHandlerAsyncResultType(typeInfo)) { return; } if (isLegalFunctionType(typeInfo)) { 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) { if (type instanceof VoidTypeInfo) { return; } if (isLegalNonCallableReturnType(type)) { return; } if (isLegalHandlerType(type)) { return; } if (isLegalHandlerAsyncResultType(type)) { return; } throw new GenException(elem, "type " + type + " is not legal for use for a return type in code generation"); } /** * The <i>Return</i> set but not `void`. */ private boolean isLegalNonCallableReturnType(TypeInfo type) { if (type.getKind().basic) { return true; } if (type.getKind().json) { return true; } if (isLegalDataObjectTypeReturn(type)) { return true; } if (isLegalEnum(type)) { return true; } if (type.getKind() == ClassKind.THROWABLE) { return true; } if (isTypeVariable(type)) { return true; } if (type.getKind() == ClassKind.OBJECT) { return true; } if (isVertxGenInterface(type, true)) { return true; } if (isLegalContainerReturn(type)) { return true; } return false; } private boolean isLegalEnum(TypeInfo info) { return info.getKind() == ClassKind.ENUM; } /** * The set <i>Param</i> */ private boolean isLegalNonCallableParam(TypeInfo typeInfo) { 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 (isLegalContainerParam(typeInfo)) { return true; } return false; } private boolean isTypeVariable(TypeInfo type) { return type instanceof TypeVariableInfo; } private boolean isLegalDataObjectTypeParam(TypeInfo type) { if (type.getKind() == ClassKind.DATA_OBJECT) { DataObjectTypeInfo classType = (DataObjectTypeInfo) type; return !classType.isAbstract(); } return false; } private boolean isLegalClassTypeParam(ExecutableElement elt, TypeInfo type) { if (type.getKind() == ClassKind.CLASS_TYPE && type.isParameterized()) { ParameterizedTypeInfo parameterized = (ParameterizedTypeInfo) type; TypeInfo arg = parameterized.getArg(0); if (arg.isVariable()) { TypeVariableInfo variable = (TypeVariableInfo) arg; for (TypeParameterElement typeParamElt : elt.getTypeParameters()) { if (typeParamElt.getSimpleName().toString().equals(variable.getName())) { return true; } } } } return false; } protected boolean isLegalDataObjectTypeReturn(TypeInfo type) { if (type.getKind() == ClassKind.DATA_OBJECT) { TypeElement typeElt = elementUtils.getTypeElement(type.getName()); if (typeElt != null) { Optional<ExecutableElement> 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(); return opt.isPresent(); } } return false; } protected boolean isLegalContainerParam(TypeInfo type) { // List<T> and Set<T> are also legal for params if T = basic type, json, @VertxGen, @DataObject // Map<K,V> 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 (argument.getKind().basic || argument.getKind().json || isVertxGenInterface(argument, false) || 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); if (argument.getKind().basic || argument.getKind().json || isVertxGenInterface(argument, false)) { return true; } } } return false; } protected boolean isLegalContainerReturn(TypeInfo type) { if (rawTypeIs(type, List.class, Set.class, Map.class)) { List<TypeInfo> args = ((ParameterizedTypeInfo) type).getArgs(); if (type.getKind() == ClassKind.MAP) { if (args.get(0).getKind() != ClassKind.STRING) { return false; } TypeInfo valueType = args.get(1); if (valueType.getKind().basic || valueType.getKind().json) { return true; } } else { TypeInfo valueType = args.get(0); if (valueType.getKind().basic || valueType.getKind().json || valueType.getKind() == ClassKind.ENUM || isVertxGenInterface(valueType, false) || isLegalDataObjectTypeReturn(valueType)) { return true; } } } return false; } private boolean isVertxGenInterface(TypeInfo type, boolean allowParameterized) { if (type.getKind() == ClassKind.API) { if (type.isParameterized()) { if (allowParameterized) { 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 false; } } else { return true; } } return false; } private boolean isLegalFunctionType(TypeInfo typeInfo) { if (typeInfo.getErased().getKind() == ClassKind.FUNCTION) { TypeInfo paramType = ((ParameterizedTypeInfo) typeInfo).getArgs().get(0); if (isLegalCallbackValueType(paramType) || paramType.getKind() == ClassKind.THROWABLE) { TypeInfo returnType = ((ParameterizedTypeInfo) typeInfo).getArgs().get(1); return isLegalNonCallableParam(returnType); } } return false; } private boolean isLegalHandlerType(TypeInfo type) { if (type.getErased().getKind() == ClassKind.HANDLER) { TypeInfo eventType = ((ParameterizedTypeInfo) type).getArgs().get(0); if (isLegalCallbackValueType(eventType) || eventType.getKind() == ClassKind.THROWABLE) { return true; } } return false; } private boolean isLegalHandlerAsyncResultType(TypeInfo type) { if (type.getErased().getKind() == ClassKind.HANDLER) { TypeInfo eventType = ((ParameterizedTypeInfo) type).getArgs().get(0); if (eventType.getErased().getKind() == ClassKind.ASYNC_RESULT && !eventType.isNullable()) { TypeInfo resultType = ((ParameterizedTypeInfo) eventType).getArgs().get(0); if (isLegalCallbackValueType(resultType)) { return true; } } } return false; } private boolean isLegalCallbackValueType(TypeInfo type) { if (type.getKind() == ClassKind.VOID) { return !type.isNullable(); } return isLegalNonCallableReturnType(type); } private void determineApiTypes() { collectedTypes.stream(). map(ClassTypeInfo::getRaw). flatMap(Helper.instanceOf(ClassTypeInfo.class)). filter(t -> !t.getPackageName().equals(ifaceFQCN)). forEach(importedTypes::add); collectedTypes.stream(). map(ClassTypeInfo::getRaw). flatMap(Helper.instanceOf(ApiTypeInfo.class)). filter(t -> !t.equals(type.getRaw())). forEach(referencedTypes::add); collectedTypes.stream(). map(ClassTypeInfo::getRaw). flatMap(Helper.instanceOf(ClassTypeInfo.class)). filter(t -> t.getKind() == ClassKind.DATA_OBJECT). forEach(referencedDataObjectTypes::add); } boolean process() { if (!processed) { traverseElem(modelElt); determineApiTypes(); processed = true; return true; } else { return false; } } private void traverseElem(Element elem) { switch (elem.getKind()) { case ENUM: case CLASS: { throw new GenException(elem, "@VertxGen can only be used with interfaces or enums in " + elem.asType().toString()); } case INTERFACE: { if (ifaceFQCN != null) { throw new GenException(elem, "Can only have one interface per file"); } type = typeFactory.create(elem.asType()).getRaw(); Helper.checkUnderModule(this, "@VertxGen"); ifaceFQCN = elem.asType().toString(); ifaceSimpleName = elem.getSimpleName().toString(); ifacePackageName = elementUtils.getPackageOf(elem).getQualifiedName().toString(); ifaceComment = elementUtils.getDocComment(elem); doc = docFactory.createDoc(elem); concrete = elem.getAnnotation(VertxGen.class) == null || elem.getAnnotation(VertxGen.class).concrete(); DeclaredType tm = (DeclaredType) elem.asType(); List<? extends TypeMirror> typeArgs = tm.getTypeArguments(); for (TypeMirror typeArg : typeArgs) { TypeVariable varTypeArg = (TypeVariable) typeArg; if (!isObjectBound(varTypeArg.getUpperBound())) { throw new GenException(elem, "Type variable bounds not supported " + varTypeArg.getUpperBound()); } } List<? extends TypeMirror> st = typeUtils.directSupertypes(tm); for (TypeMirror tmSuper: st) { if (!tmSuper.toString().equals(Object.class.getName())) { TypeInfo superTypeInfo; try { superTypeInfo = typeFactory.create(tmSuper); } catch (IllegalArgumentException e) { throw new GenException(elem, e.getMessage()); } switch (superTypeInfo.getKind()) { case API: { try { ApiTypeInfo superType = (ApiTypeInfo) typeFactory.create(tmSuper).getRaw(); if (superType.isConcrete()) { if (concrete) { if (concreteSuperType != null) { throw new GenException(elem, "A concrete interface cannot extend more than one concrete interfaces"); } } else { throw new GenException(elem, "A abstract interface cannot extend a concrete interface"); } concreteSuperType = superTypeInfo; } else { abstractSuperTypes.add(superTypeInfo); } superTypes.add(superTypeInfo); } catch (Exception e) { throw new GenException(elem, e.getMessage()); } break; } } superTypeInfo.collectImports(collectedTypes); } } break; } } // Traverse nested elements that are not methods (like nested interfaces) for (Element enclosedElt : elem.getEnclosedElements()) { if (enclosedElt.getKind() != ElementKind.METHOD) { traverseElem(enclosedElt); } } if (elem.getKind() == ElementKind.INTERFACE) { TypeMirror objectType = elementUtils.getTypeElement("java.lang.Object").asType(); // Traverse methods elementUtils.getAllMembers((TypeElement) elem).stream(). filter(elt -> !typeUtils.isSameType(elt.getEnclosingElement().asType(), objectType)). flatMap(Helper.FILTER_METHOD). forEach(this::addMethod); boolean hasNoMethods = methods.values().stream().filter(m -> !m.isDefaultMethod()).count() == 0; if (hasNoMethods && superTypes.isEmpty()) { throw new GenException(elem, "Interface " + ifaceFQCN + " does not contain any methods for generation"); } sortMethodMap(methodMap); // Now check for overloaded methods for (List<MethodInfo> meths: methodMap.values()) { // Ambiguous try { methodOverloadChecker.checkAmbiguous(meths); } catch (RuntimeException e) { throw new GenException(elem, e.getMessage()); } // Cannot be both static and non static MethodInfo first = meths.get(0); for (MethodInfo method : meths) { if (method.staticMethod != first.staticMethod) { throw new GenException(elem, "Overloaded method " + method.getName() + " cannot be both static and instance"); } } } } } private void addMethod(ExecutableElement modelMethod) { boolean isIgnore = modelMethod.getAnnotation(GenIgnore.class) != null; if (isIgnore) { return; } Set<Modifier> mods = modelMethod.getModifiers(); if (!mods.contains(Modifier.PUBLIC)) { return; } TypeElement declaringElt = (TypeElement) modelMethod.getEnclosingElement(); TypeInfo declaringType = typeFactory.create(declaringElt.asType()); if (!declaringElt.equals(modelElt) && (declaringType.getKind() != ClassKind.API && declaringType.getKind() != ClassKind.HANDLER)) { return; } 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<TypeParamInfo.Method> 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<Method> reflectMethods; List<ExecutableElement> modelMethods; Method reflectMethod = Helper.getReflectMethod(modelMethod); if (reflectMethod != null) { reflectMethods = new ArrayList<>(); reflectMethods.add(reflectMethod); modelMethods = null; } else { reflectMethods = null; modelMethods = new ArrayList<>(); modelMethods.add(modelMethod); } // Owner types Set<ClassTypeInfo> ownerTypes = new HashSet<>(); ownerTypes.add(type); ArrayList<DeclaredType> 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 -> { if (reflectMethods != null) { Method overridenMethodRef = Helper.getReflectMethod(overridenMethodElt); if (overridenMethodRef != null) { reflectMethods.add(overridenMethodRef); } } else { modelMethods.add(overridenMethodElt); } ownerTypes.add(typeFactory.create((DeclaredType) ancestorElt.asType()).getRaw()); }); } } // Map<String, String> paramDescs = new HashMap<>(); String comment = elementUtils.getDocComment(modelMethod); Doc doc = docFactory.createDoc(modelMethod); Text returnDesc = 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<Tag> 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)); } } // List<ParamInfo> mParams = getParams(reflectMethods, modelMethods, modelMethod, paramDescs); // 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; if (reflectMethods != null) { returnTypeUse = TypeUse.createTypeUse(reflectMethods.stream().map(Method::getAnnotatedReturnType).toArray(AnnotatedType[]::new)); } else { returnTypeUse = TypeUse.createTypeUse(modelMethods.stream().map(ExecutableElement::getReturnType).toArray(TypeMirror[]::new)); } 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 instanceof VoidTypeInfo) { 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()); } else if (returnType.isNullable()) { throw new GenException(modelMethod, "Fluent return type cannot be nullable"); } // Determine method kind + validate MethodKind kind = MethodKind.OTHER; int lastParamIndex = mParams.size() - 1; if (lastParamIndex >= 0 && (returnType instanceof VoidTypeInfo || isFluent)) { TypeInfo lastParamType = mParams.get(lastParamIndex).type; if (lastParamType.getKind() == ClassKind.HANDLER) { TypeInfo typeArg = ((ParameterizedTypeInfo) lastParamType).getArgs().get(0); if (typeArg.getKind() == ClassKind.ASYNC_RESULT) { kind = MethodKind.FUTURE; } else { kind = MethodKind.HANDLER; } } } MethodInfo methodInfo = createMethodInfo(ownerTypes, methodName, comment, doc, kind, returnType, returnDesc, isFluent, isCacheReturn, mParams, modelMethod, isStatic, isDefault, typeParams, declaringElt); checkMethod(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<ExecutableElement, MethodInfo> 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().ownerTypes.addAll(methodInfo.ownerTypes); return; } } } // Add the method List<MethodInfo> methodsByName = methodMap.get(methodInfo.getName()); if (methodsByName == null) { methodsByName = new ArrayList<>(); methodMap.put(methodInfo.getName(), methodsByName); } methodsByName.add(methodInfo); methodInfo.collectImports(collectedTypes); if (!declaringElt.equals(modelElt) && declaringType.getKind() == ClassKind.API) { ApiTypeInfo declaringApiType = (ApiTypeInfo) declaringType.getRaw(); if (declaringApiType.isConcrete()) { if (typeUtils.isSameType(methodType, modelMethod.asType())) { return; } } } methods.put(modelMethod, methodInfo); } // This is a hook to allow a specific type of method to be created protected MethodInfo createMethodInfo(Set<ClassTypeInfo> ownerTypes, String methodName, String comment, Doc doc, MethodKind kind, TypeInfo returnType, Text returnDescription, boolean isFluent, boolean isCacheReturn, List<ParamInfo> mParams, ExecutableElement methodElt, boolean isStatic, boolean isDefault, ArrayList<TypeParamInfo.Method> typeParams, TypeElement declaringElt) { return new MethodInfo(ownerTypes, methodName, kind, returnType, returnDescription, isFluent, isCacheReturn, mParams, comment, doc, isStatic, isDefault, typeParams); } // This is a hook to allow different model implementations to check methods in different ways protected void checkMethod(MethodInfo methodInfo) { List<MethodInfo> methodsByName = methodMap.get(methodInfo.getName()); if (methodsByName != null) { // Overloaded methods must have same return type for (MethodInfo meth: methodsByName) { if (!meth.returnType.equals(methodInfo.returnType)) { throw new GenException(this.modelElt, "Overloaded method " + methodInfo.name + " must have the same return type " + meth.returnType + " != " + methodInfo.returnType); } } } } private boolean isObjectBound(TypeMirror bound) { return bound.getKind() == TypeKind.DECLARED && bound.toString().equals(Object.class.getName()); } private List<ParamInfo> getParams(List<Method> reflectMethods, List<ExecutableElement> modelMethods, ExecutableElement methodElt, Map<String, String> descs) { ExecutableType methodType = (ExecutableType) typeUtils.asMemberOf((DeclaredType) modelElt.asType(), methodElt); List<? extends VariableElement> params = methodElt.getParameters(); List<ParamInfo> mParams = new ArrayList<>(); for (int i = 0; i < params.size();i++) { VariableElement param = params.get(i); TypeMirror type = methodType.getParameterTypes().get(i); TypeInfo typeInfo; int index = i; TypeUse typeUse; if (reflectMethods != null) { typeUse = TypeUse.createTypeUse(reflectMethods.stream().map(m -> m.getAnnotatedParameterTypes()[index]).toArray(AnnotatedType[]::new)); } else { typeUse = TypeUse.createTypeUse(modelMethods.stream().map(m -> m.getParameters().get(index).asType()).toArray(TypeMirror[]::new)); } try { typeInfo = typeFactory.create(typeUse, type); } catch (Exception e) { throw new GenException(param, e.getMessage()); } checkParamType(methodElt, type, typeInfo, i, params.size()); String name = param.getSimpleName().toString(); String desc = descs.get(name); Text text = desc != null ? new Text(desc).map(Token.tagMapper(elementUtils, typeUtils, modelElt)) : null; ParamInfo mParam = new ParamInfo(i, name, text, typeInfo); mParams.add(mParam); } return mParams; } @Override public Map<String, Object> getVars() { Map<String, Object> vars = Model.super.getVars(); vars.put("importedTypes", getImportedTypes()); vars.put("concrete", isConcrete()); vars.put("type", getType()); vars.put("ifacePackageName", getIfacePackageName()); vars.put("ifaceSimpleName", getIfaceSimpleName()); vars.put("ifaceFQCN", getIfaceFQCN()); vars.put("ifaceComment", getIfaceComment()); vars.put("doc", doc); vars.put("methods", getMethods()); vars.put("referencedTypes", getReferencedTypes()); vars.put("superTypes", getSuperTypes()); vars.put("concreteSuperType", getConcreteSuperType()); vars.put("abstractSuperTypes", getAbstractSuperTypes()); vars.put("handlerType", getHandlerType()); vars.put("methodsByName", getMethodMap()); vars.put("referencedDataObjectTypes", getReferencedDataObjectTypes()); vars.put("typeParams", getTypeParams()); vars.put("instanceMethods", getInstanceMethods()); vars.put("staticMethods", getStaticMethods()); return vars; } private 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; } }