io.smallrye.mutiny.vertx.codegen.MutinyGenerator Maven / Gradle / Ivy
The newest version!
package io.smallrye.mutiny.vertx.codegen;
import io.smallrye.mutiny.vertx.TypeArg;
import io.smallrye.mutiny.vertx.codegen.lang.*;
import io.smallrye.mutiny.vertx.codegen.methods.*;
import io.vertx.codegen.*;
import io.vertx.codegen.annotations.ModuleGen;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.codegen.type.ClassTypeInfo;
import io.vertx.codegen.type.ParameterizedTypeInfo;
import io.vertx.codegen.type.TypeInfo;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import javax.tools.Diagnostic;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.concurrent.Flow;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static io.vertx.codegen.type.ClassKind.API;
import static java.util.stream.Collectors.joining;
public class MutinyGenerator extends Generator {
public static final String ID = "mutiny";
private List methods = new ArrayList<>();
private final Map> methodTypeArgMap = new HashMap<>();
public static List IGNORED_TYPES = Arrays.asList(
Future.class.getName(),
CompositeFuture.class.getName()
);
MutinyGenerator() {
this.kinds = Collections.singleton("class");
this.name = "mutiny";
}
protected void genMethods(ClassModel model, MethodInfo method, List cacheDecls, PrintWriter writer) {
generateMethod(model, method, cacheDecls, writer);
MethodInfo publisherOverload = genOverloadedMethod(method);
if (publisherOverload != null) {
generateMethod(model, publisherOverload, cacheDecls, writer);
}
}
private MethodInfo genOverloadedMethod(MethodInfo method) {
List params = null;
int count = 0;
for (ParamInfo param : method.getParams()) {
if (param.getType().isParameterized()
&& param.getType().getRaw().getName().equals("io.vertx.core.streams.ReadStream")) {
if (params == null) {
params = new ArrayList<>(method.getParams());
}
ParameterizedTypeInfo paramType = new io.vertx.codegen.type.ParameterizedTypeInfo(
io.vertx.codegen.type.TypeReflectionFactory.create(Flow.Publisher.class).getRaw(),
false,
Collections.singletonList(((ParameterizedTypeInfo) param.getType()).getArg(0)));
params.set(count, new io.vertx.codegen.ParamInfo(
param.getIndex(),
param.getName(),
param.getDescription(),
paramType));
}
count = count + 1;
}
if (params != null) {
return method.copy().setParams(params);
}
return null;
}
@Override
public Collection> annotations() {
return Arrays.asList(VertxGen.class, ModuleGen.class);
}
@Override
public String filename(ClassModel model) {
ModuleInfo module = model.getModule();
return module.translateQualifiedName(model.getFqn(), ID) + ".java";
}
@Override
public String render(ClassModel model, int index, int size, Map session) {
if (IGNORED_TYPES.contains(model.getFqn())) {
return null;
}
initState(model);
StringWriter sw = new StringWriter();
PrintWriter writer = new PrintWriter(sw);
generateClass(model, writer);
return sw.toString();
}
private void initState(ClassModel model) {
initGenMethods(model);
initCachedTypeArgs();
}
private final List generators = Arrays.asList(
new PackageDeclarationCodeWriter(),
new ImportDeclarationCodeWriter(),
new ClassJavadocCodeWriter(),
new MutinyGenAnnotationCodeWriter(),
new ClassDeclarationCodeWriter(),
// Class body start here
new TypeArgsConstantCodeWriter(),
new DelegateFieldCodeWriter(),
new ConstructorWithDelegateParameterCodeWriter(),
new ConstructorWithObjectDelegateCodeWriter(),
new ConstructorWithGenericTypesCodeWriter(),
new NoArgConstructorCodeWriter(),
new GetDelegateMethodCodeWriter(),
(mode, writer) ->
methodTypeArgMap.forEach((method, map) -> map.forEach(
(typeArg, identifier) -> genTypeArgDecl(typeArg, method, identifier, writer))),
new DelegateMethodDeclarationCodeWriter(),
new BufferRelatedMethodCodeWriter(),
new ToStringMethodCodeWriter(),
new HashCodeAndEqualsMethodsCodeWriter(),
new IterableMethodCodeWriter(),
new IteratorMethodsCodeWriter(),
new FunctionApplyMethodCodeWriter(),
new ToSubscriberCodeWriter(),
new ReadStreamMethodDeclarationCodeWriter(),
(model, writer) -> {
if (model.isConcrete()) {
generateClassBody(model, writer);
} else {
methods.forEach(method -> generateMethodDeclaration(model, method, Collections.emptyList(), writer));
}
},
new ToMultiMethodCodeWriter(),
new ConsumerMethodCodeWriter(),
new NewInstanceMethodCodeWriter(),
new NewInstanceWithGenericsMethodCodeWriter(),
(model, writer) -> writer.println("}"), // end of class
new ImplClassCodeWriter(this)
);
private void generateClass(ClassModel model, PrintWriter writer) {
generators.forEach(cw -> cw.apply(model, writer));
}
public void generateClassBody(ClassModel model, PrintWriter writer) {
List cacheDecls = new ArrayList<>();
// This list filters out method that conflict during the generation
methods.forEach(method -> genMethods(model, method, cacheDecls, writer));
new ConstantCodeWriter(methodTypeArgMap).apply(model, writer);
for (String cacheDecl : cacheDecls) {
writer.print(" ");
writer.print(cacheDecl);
writer.println(";");
}
}
/**
* Compute the list of methods.
*
* @param model the class model
*/
private void initGenMethods(ClassModel model) {
List> list = new ArrayList<>();
List infos = model.getMethods().stream()
// Remove method returning Future as it conflicts with method returning Uni
.filter(mi -> !mi.getReturnType().getName().equals(Future.class.getName()))
// Remove methods coming from ignored type
.filter(mi -> {
for (ClassTypeInfo ownerType : mi.getOwnerTypes()) {
if (IGNORED_TYPES.contains(ownerType.getName())) {
return false;
}
}
return true;
})
.collect(Collectors.toList());
list.add(infos);
list.add(model.getAnyJavaTypeMethods());
list.forEach(methods -> {
// First pass: filter conflicting overrides, that will partly filter it
ListIterator it = methods.listIterator();
while (it.hasNext()) {
MethodInfo method = it.next();
if (CodeGenHelper.methodKind(method) != MethodKind.CALLBACK) {
// Has it been removed above ?
Predicate pred;
if (method.isOwnedBy(model.getType())) {
pred = other -> isOverride(method, other);
} else {
pred = other -> isOverride(method, other);
}
if (methods.stream()
.filter(m -> CodeGenHelper.methodKind(m) == MethodKind.CALLBACK)
.anyMatch(pred)) {
it.remove();
}
}
}
// Second pass: filter future methods that might be still conflict
it = methods.listIterator();
while (it.hasNext()) {
MethodInfo meth = it.next();
if (CodeGenHelper.methodKind(meth) == MethodKind.CALLBACK) {
boolean remove;
List abc = model.getMethodMap().getOrDefault(meth.getName(), Collections.emptyList());
if (meth.isOwnedBy(model.getType())) {
remove = abc.stream()
.filter(m -> CodeGenHelper.methodKind(m) != MethodKind.CALLBACK && isOverride(m, meth))
.anyMatch(m -> !m.isOwnedBy(model.getType()) || methods.contains(m));
} else {
remove = abc.stream()
.filter(other -> CodeGenHelper.methodKind(other) != MethodKind.CALLBACK)
.anyMatch(other -> {
if (CodeGenHelper.methodKind(other) != MethodKind.CALLBACK) {
Set tmp = new HashSet<>(other.getOwnerTypes());
tmp.retainAll(meth.getOwnerTypes());
return isOverride(meth, other) & !tmp.isEmpty();
}
return false;
});
}
if (remove) {
it.remove();
}
}
}
});
methods = list.stream().flatMap(Collection::stream).collect(Collectors.toList());
}
/**
* Build the map of type arguments that can be statically cached within the generated class
*/
private void initCachedTypeArgs() {
methodTypeArgMap.clear();
int count = 0;
for (MethodInfo method : methods) {
TypeInfo returnType = method.getReturnType();
if (returnType instanceof ParameterizedTypeInfo) {
ParameterizedTypeInfo parameterizedType = (ParameterizedTypeInfo) returnType;
List typeArgs = parameterizedType.getArgs();
Map typeArgMap = new HashMap<>();
for (TypeInfo typeArg : typeArgs) {
if (typeArg.getKind() == API && !containsTypeVariableArgument(typeArg)) {
String typeArgRef = "TYPE_ARG_" + count++;
typeArgMap.put(typeArg, typeArgRef);
}
}
methodTypeArgMap.put(method, typeArgMap);
}
}
}
/**
* @return whether a type contains a nested type variable declaration
*/
private boolean containsTypeVariableArgument(TypeInfo type) {
if (type.isVariable()) {
return true;
} else if (type.isParameterized()) {
List typeArgs = ((ParameterizedTypeInfo) type).getArgs();
for (TypeInfo typeArg : typeArgs) {
if (typeArg.isVariable()) {
return true;
} else if (typeArg.isParameterized() && containsTypeVariableArgument(typeArg)) {
return true;
}
}
}
return false;
}
private boolean isOverride(MethodInfo s1, MethodInfo s2) {
if (s1.getName().equals(s2.getName()) && s1.getParams().size() == s2.getParams().size() - 1) {
for (int i = 0; i < s1.getParams().size(); i++) {
if (!s1.getParams().get(i).getType().equals(s2.getParams().get(i).getType())) {
return false;
}
}
return true;
}
return false;
}
final void generateMethod(ClassModel model, MethodInfo method, List cacheDecls, PrintWriter writer) {
UniMethodGenerator uni = new UniMethodGenerator(writer, methodTypeArgMap);
ForgetMethodGenerator forget = new ForgetMethodGenerator(writer);
AwaitMethodGenerator await = new AwaitMethodGenerator(writer);
ConsumerMethodGenerator consumer = new ConsumerMethodGenerator(writer);
SimpleMethodGenerator simple = new SimpleMethodGenerator(writer, cacheDecls, methodTypeArgMap);
if (CodeGenHelper.methodKind(method) == MethodKind.CALLBACK) {
uni.generate(model, method);
await.generate(method);
forget.generate(model, method);
} else if (CodeGenHelper.methodKind(method) == MethodKind.HANDLER) {
simple.generate(model, method);
consumer.generate(method);
} else if (CodeGenHelper.methodKind(method) == MethodKind.OTHER) {
if (isMethodReturningAFuture(method)) {
env.getMessager().printMessage(Diagnostic.Kind.WARNING,
"A method returning a 'Future' has been found - missing handler method for '" + method.getName()
+ "' declared in " + method.getOwnerTypes().stream().map(TypeInfo::getName)
.collect(joining()));
uni.generateOther(method);
await.generateOther(method);
forget.generateOther(model, method);
} else {
simple.generateOther(model, method);
}
}
}
private boolean isMethodReturningAFuture(MethodInfo method) {
return method.getReturnType() != null && method.getReturnType().getRaw() != null && method.getReturnType()
.getRaw().getName().equals(Future.class.getName());
}
private void generateMethodDeclaration(ClassModel model, MethodInfo method, List cacheDecls,
PrintWriter writer) {
if (CodeGenHelper.methodKind(method) == MethodKind.CALLBACK) {
new UniMethodGenerator(writer, methodTypeArgMap).generateDeclaration(method);
if (model.getMethods().stream()
.noneMatch(mi -> mi.getName().equals(method.getName() + AwaitMethodGenerator.SUFFIX_AND_AWAIT))) {
new AwaitMethodGenerator(writer).generateDeclaration(method);
}
if (model.getMethods().stream()
.noneMatch(mi -> mi.getName().equals(method.getName() + ForgetMethodGenerator.SUFFIX_AND_FORGET))) {
new ForgetMethodGenerator(writer).generateDeclaration(model, method);
}
} else if (CodeGenHelper.methodKind(method) == MethodKind.HANDLER) {
ConsumerMethodGenerator consumer = new ConsumerMethodGenerator(writer);
consumer.generateDeclaration(method);
} else {
SimpleMethodGenerator simple = new SimpleMethodGenerator(writer, cacheDecls, methodTypeArgMap);
simple.generateDeclaration(method);
}
}
private void genTypeArgDecl(TypeInfo typeArg, MethodInfo method, String typeArgRef, PrintWriter writer) {
StringBuilder sb = new StringBuilder();
CodeGenHelper.genTypeArg(typeArg, method, 1, sb);
writer.print(" static final ");
writer.print(TypeArg.class.getName());
writer.print("<");
writer.print(typeArg.translateName(ID));
writer.print("> ");
writer.print(typeArgRef);
writer.print(" = ");
writer.print(sb);
writer.println(";");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy