diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index d25cfa49e0de5..e978cc97db37a 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -2425,7 +2425,11 @@ TIP: Quarkus detects possible namespace collisions and fails the build if a spec === Global Variables The `io.quarkus.qute.TemplateGlobal` annotation can be used to denote static fields and methods that supply _global variables_ which are accessible in any template. -Internally, each global variable is added to the data map of any `TemplateInstance` via the `TemplateInstance#data(String, Object)` method. + +Global variables are: + +* added to the data map of any `TemplateInstance` during initialization, +* accessible with the `global:` namespace. .Global Variables Definition [source,java] @@ -2454,11 +2458,11 @@ public class Globals { [source,html] ---- User: {currentUser} <1> -Age: {age} <2> +Age: {global:age} <2> Colors: {#each myColors}{it}{#if it_hasNext}, {/if}{/each} <3> ---- <1> `currentUser` resolves to `Globals#user()`. -<2> `age` resolves to `Globals#age`. +<2> The `global:` namespace is used; `age` resolves to `Globals#age`. <3> `myColors` resolves to `Globals#myColors()`. NOTE: Note that global variables implicitly add <> to all templates and so any expression that references a global variable is validated during build. @@ -2473,7 +2477,7 @@ Colors: RED, BLUE ==== Resolving Conflicts -Global variables may conflict with regular data objects. +If not accessed via the `global:` namespace the global variables may conflict with regular data objects. <> override the global variables automatically. For example, the following definition overrides the global variable supplied by the `Globals#user()` method: diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java index 2a67ca6932137..12477cf517114 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java @@ -435,7 +435,8 @@ void validateMessageBundleMethodsInTemplates(TemplatesAnalysisBuildItem analysis List checkedTemplates, BeanDiscoveryFinishedBuildItem beanDiscovery, List templateData, - QuteConfig config) { + QuteConfig config, + List globals) { IndexView index = beanArchiveIndex.getIndex(); Function templateIdToPathFun = new Function() { @@ -585,7 +586,7 @@ public String apply(String id) { implicitClassToMembersUsed, templateIdToPathFun, generatedIdsToMatches, extensionMethodExcludes, checkedTemplate, lookupConfig, namedBeans, namespaceTemplateData, regularExtensionMethods, namespaceExtensionMethods, - assignabilityCheck); + assignabilityCheck, globals); MatchResult match = results.get(param.toOriginalString()); if (match != null && !match.isEmpty() && !assignabilityCheck.isAssignableFrom(match.type(), methodParams.get(idx))) { diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index adc3c78166a5c..c2fc7b7393167 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -153,6 +153,7 @@ public class QuteProcessor { public static final DotName LOCATION = Names.LOCATION; + public static final String GLOBAL_NAMESPACE = "global"; private static final Logger LOGGER = Logger.getLogger(QuteProcessor.class); @@ -922,7 +923,8 @@ void validateExpressions(TemplatesAnalysisBuildItem templatesAnalysis, List checkedTemplates, List templateData, QuteConfig config, - PackageConfig packageConfig) { + PackageConfig packageConfig, + List globals) { long start = System.nanoTime(); @@ -996,7 +998,7 @@ public String apply(String id) { incorrectExpressions, expression, index, implicitClassToMembersUsed, templateIdToPathFun, generatedIdsToMatches, extensionMethodExcludes, checkedTemplate, lookupConfig, namedBeans, namespaceTemplateData, regularExtensionMethods, - namespaceExtensionMethods, assignabilityCheck); + namespaceExtensionMethods, assignabilityCheck, globals); generatedIdsToMatches.put(expression.getGeneratedId(), match); } @@ -1130,7 +1132,8 @@ static MatchResult validateNestedExpressions(QuteConfig config, TemplateAnalysis Map namespaceTemplateData, List regularExtensionMethods, Map> namespaceToExtensionMethods, - AssignabilityCheck assignabilityCheck) { + AssignabilityCheck assignabilityCheck, + List globals) { LOGGER.debugf("Validate %s from %s", expression, expression.getOrigin()); @@ -1140,7 +1143,7 @@ static MatchResult validateNestedExpressions(QuteConfig config, TemplateAnalysis validateParametersOfNestedVirtualMethods(config, templateAnalysis, results, excludes, incorrectExpressions, expression, index, implicitClassToMembersUsed, templateIdToPathFun, generatedIdsToMatches, extensionMethodExcludes, checkedTemplate, lookupConfig, namedBeans, namespaceTemplateData, regularExtensionMethods, - namespaceToExtensionMethods, assignabilityCheck); + namespaceToExtensionMethods, assignabilityCheck, globals); MatchResult match = new MatchResult(assignabilityCheck); @@ -1148,7 +1151,8 @@ static MatchResult validateNestedExpressions(QuteConfig config, TemplateAnalysis // Process the namespace // ====================== NamespaceResult namespaceResult = processNamespace(expression, match, index, incorrectExpressions, namedBeans, results, - templateAnalysis, namespaceTemplateData, lookupConfig, namespaceToExtensionMethods, templateIdToPathFun); + templateAnalysis, namespaceTemplateData, lookupConfig, namespaceToExtensionMethods, templateIdToPathFun, + globals); if (namespaceResult.ignoring) { return match; } @@ -1326,19 +1330,44 @@ private static RootResult processRoot(Expression expression, MatchResult match, ignoring = true; } } else { - // No namespace extension method found - incorrect expression - incorrectExpressions.produce(new IncorrectExpressionBuildItem(expression.toOriginalString(), - String.format("No matching namespace [%s] extension method found", namespace.namespace), - expression.getOrigin())); - match.clearValues(); - putResult(match, results, expression); - ignoring = true; + if (namespace.hasGlobal()) { + ClassInfo variableClass = index.getClassByName(namespace.global.getVariableType().name()); + if (variableClass != null) { + match.setValues(variableClass, namespace.global.getVariableType()); + iterator = processHintsIfNeeded(root, iterator, parts, templateAnalysis, root.asHintInfo().hints, match, + index, expression, generatedIdsToMatches, incorrectExpressions); + } else { + // Global variable type not available + putResult(match, results, expression); + ignoring = true; + } + } else { + // No global and no namespace extension method found - incorrect expression + incorrectExpressions.produce(new IncorrectExpressionBuildItem(expression.toOriginalString(), + String.format("No matching namespace [%s] extension method found", namespace.namespace), + expression.getOrigin())); + match.clearValues(); + putResult(match, results, expression); + ignoring = true; + } } } else if (namespace.hasDataNamespaceInfo()) { // Validate as Data namespace expression has parameter declaration bound to the variable // Skip the first part, e.g. for {data:item.name} we start validation with "name" match.setValues(namespace.dataNamespaceExpTypeInfo.rawClass, namespace.dataNamespaceExpTypeInfo.resolvedType); + } else if (namespace.hasGlobal()) { + // "global:" namespace is used and no namespace extension methods exist + ClassInfo variableClass = index.getClassByName(namespace.global.getVariableType().name()); + if (variableClass != null) { + match.setValues(variableClass, namespace.global.getVariableType()); + iterator = processHintsIfNeeded(root, iterator, parts, templateAnalysis, root.asHintInfo().hints, match, + index, expression, generatedIdsToMatches, incorrectExpressions); + } else { + // Global variable type not available + putResult(match, results, expression); + ignoring = true; + } } else if (rootClazz == null) { // No namespace is used or no declarative resolver (extension methods, @TemplateData, etc.) if (root.isTypeInfo()) { @@ -1416,7 +1445,8 @@ private static NamespaceResult processNamespace(Expression expression, MatchResu Map results, TemplateAnalysis templateAnalysis, Map namespaceTemplateData, JavaMemberLookupConfig lookupConfig, Map> namespaceToExtensionMethods, - Function templateIdToPathFun) { + Function templateIdToPathFun, + List globals) { String namespace = expression.getNamespace(); if (namespace == null) { return NamespaceResult.EMPTY; @@ -1426,6 +1456,10 @@ private static NamespaceResult processNamespace(Expression expression, MatchResu TemplateDataBuildItem templateData = null; List namespaceExtensionMethods = null; boolean ignored = false; + TemplateGlobalBuildItem global = namespace.equals(GLOBAL_NAMESPACE) + ? globals.stream().filter(g -> g.getName().equals(expression.getParts().get(0).getName())).findFirst() + .orElse(null) + : null; if (namespace.equals(INJECT_NAMESPACE) || namespace.equals(CDI_NAMESPACE)) { // cdi:, inject: @@ -1475,22 +1509,26 @@ private static NamespaceResult processNamespace(Expression expression, MatchResu filter = filter.and(templateData::filter); lookupConfig = new FirstPassJavaMemberLookupConfig(lookupConfig, filter, true); } else { - // Extension methods exist for the given namespace + // Extension methods may exist for the given namespace namespaceExtensionMethods = namespaceToExtensionMethods.get(namespace); + if (namespaceExtensionMethods == null) { - // All other namespaces are ignored - putResult(match, results, expression); - ignored = true; + if (!namespace.equals(GLOBAL_NAMESPACE) || global == null) { + // Not "global:" with a matching global variable + // All other namespaces are ignored + putResult(match, results, expression); + ignored = true; + } } } } return new NamespaceResult(namespace, rootClazz, dataNamespaceTypeInfo, templateData, namespaceExtensionMethods, - ignored, lookupConfig); + ignored, lookupConfig, global); } private static class NamespaceResult { - static final NamespaceResult EMPTY = new NamespaceResult(null, null, null, null, null, false, null); + static final NamespaceResult EMPTY = new NamespaceResult(null, null, null, null, null, false, null, null); private final String namespace; private final ClassInfo rootClazz; @@ -1499,11 +1537,12 @@ private static class NamespaceResult { private final List extensionMethods; private final boolean ignoring; private final JavaMemberLookupConfig lookupConfig; + private final TemplateGlobalBuildItem global; NamespaceResult(String namespace, ClassInfo rootClazz, TypeInfo dataNamespaceExpTypeInfo, - TemplateDataBuildItem templateData, - List namespaceExtensionMethods, boolean ignoring, - JavaMemberLookupConfig lookupConfig) { + TemplateDataBuildItem templateData, List namespaceExtensionMethods, + boolean ignoring, + JavaMemberLookupConfig lookupConfig, TemplateGlobalBuildItem global) { this.namespace = namespace; this.rootClazz = rootClazz; this.dataNamespaceExpTypeInfo = dataNamespaceExpTypeInfo; @@ -1511,6 +1550,7 @@ private static class NamespaceResult { this.extensionMethods = namespaceExtensionMethods; this.ignoring = ignoring; this.lookupConfig = lookupConfig; + this.global = global; } boolean hasExtensionMethods() { @@ -1529,6 +1569,10 @@ boolean hasLookupConfig() { return lookupConfig != null; } + boolean hasGlobal() { + return global != null; + } + boolean isIn(String... values) { for (String value : values) { if (value.equals(namespace)) { @@ -1595,7 +1639,8 @@ private static void validateParametersOfNestedVirtualMethods(QuteConfig config, Map namespaceTemplateData, List regularExtensionMethods, Map> namespaceExtensionMethods, - AssignabilityCheck assignabilityCheck) { + AssignabilityCheck assignabilityCheck, + List globals) { for (Expression.Part part : expression.getParts()) { if (part.isVirtualMethod()) { for (Expression param : part.asVirtualMethod().getParameters()) { @@ -1607,7 +1652,8 @@ private static void validateParametersOfNestedVirtualMethods(QuteConfig config, validateNestedExpressions(config, templateAnalysis, null, results, excludes, incorrectExpressions, param, index, implicitClassToMembersUsed, templateIdToPathFun, generatedIdsToMatches, extensionMethodExcludes, checkedTemplate, lookupConfig, namedBeans, - namespaceTemplateData, regularExtensionMethods, namespaceExtensionMethods, assignabilityCheck); + namespaceTemplateData, regularExtensionMethods, namespaceExtensionMethods, assignabilityCheck, + globals); } } } @@ -1834,7 +1880,7 @@ void generateValueResolvers(QuteConfig config, BuildProducer generatedResolvers, BuildProducer reflectiveClass, - BuildProducer generatedInitializers) { + BuildProducer globalProviders) { if (!incorrectExpressions.isEmpty()) { // Skip generation if a validation error occurs @@ -2004,7 +2050,7 @@ public Function apply(ClassInfo clazz) { } if (!templateGlobals.isEmpty()) { - TemplateGlobalGenerator globalGenerator = new TemplateGlobalGenerator(classOutput); + TemplateGlobalGenerator globalGenerator = new TemplateGlobalGenerator(classOutput, GLOBAL_NAMESPACE, -1000, index); Map> classToTargets = new HashMap<>(); Map> classToGlobals = templateGlobals.stream() @@ -2019,7 +2065,7 @@ public Function apply(ClassInfo clazz) { } for (String generatedType : globalGenerator.getGeneratedTypes()) { - generatedInitializers.produce(new GeneratedTemplateInitializerBuildItem(generatedType)); + globalProviders.produce(new TemplateGlobalProviderBuildItem(generatedType)); reflectiveClass.produce(ReflectiveClassBuildItem.builder(generatedType).build()); } } @@ -2399,7 +2445,7 @@ public boolean test(TypeCheck check) { void initialize(BuildProducer syntheticBeans, QuteRecorder recorder, List generatedValueResolvers, List templatePaths, Optional templateVariants, - List templateInitializers, + List templateInitializers, TemplateRootsBuildItem templateRoots) { List templates = new ArrayList<>(); @@ -2424,7 +2470,7 @@ void initialize(BuildProducer syntheticBeans, QuteRecord .supplier(recorder.createContext(generatedValueResolvers.stream() .map(GeneratedValueResolverBuildItem::getClassName).collect(Collectors.toList()), templates, tags, variants, templateInitializers.stream() - .map(GeneratedTemplateInitializerBuildItem::getClassName).collect(Collectors.toList()), + .map(TemplateGlobalProviderBuildItem::getClassName).collect(Collectors.toList()), templateRoots.getPaths().stream().map(p -> p + "/").collect(Collectors.toSet()))) .done()); } diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/GeneratedTemplateInitializerBuildItem.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateGlobalProviderBuildItem.java similarity index 60% rename from extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/GeneratedTemplateInitializerBuildItem.java rename to extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateGlobalProviderBuildItem.java index 3101fa3a59c34..d02a84ee06bef 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/GeneratedTemplateInitializerBuildItem.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateGlobalProviderBuildItem.java @@ -2,11 +2,11 @@ import io.quarkus.builder.item.MultiBuildItem; -public final class GeneratedTemplateInitializerBuildItem extends MultiBuildItem { +public final class TemplateGlobalProviderBuildItem extends MultiBuildItem { private final String className; - public GeneratedTemplateInitializerBuildItem(String className) { + public TemplateGlobalProviderBuildItem(String className) { this.className = className; } diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/globals/TemplateGlobalNamespaceValidationFailureTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/globals/TemplateGlobalNamespaceValidationFailureTest.java new file mode 100644 index 0000000000000..5e07de37c8945 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/globals/TemplateGlobalNamespaceValidationFailureTest.java @@ -0,0 +1,55 @@ +package io.quarkus.qute.deployment.globals; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.TemplateException; +import io.quarkus.qute.TemplateGlobal; +import io.quarkus.test.QuarkusUnitTest; + +public class TemplateGlobalNamespaceValidationFailureTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(Globals.class) + .addAsResource(new StringAsset( + "Hello {global:user.name}!"), + "templates/hello.txt")) + .assertException(t -> { + Throwable e = t; + TemplateException te = null; + while (e != null) { + if (e instanceof TemplateException) { + te = (TemplateException) e; + break; + } + e = e.getCause(); + } + assertNotNull(te); + assertTrue( + te.getMessage().contains("Found incorrect expressions (1)"), te.getMessage()); + assertTrue( + te.getMessage().contains( + "Property/method [name] not found on class [java.lang.String] nor handled by an extension method"), + te.getMessage()); + }); + + @Test + public void test() { + fail(); + } + + public static class Globals { + + @TemplateGlobal + static String user = "Fu"; + + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/globals/TemplateGlobalTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/globals/TemplateGlobalTest.java index de372ee5c691a..b5b0388ecca85 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/globals/TemplateGlobalTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/globals/TemplateGlobalTest.java @@ -20,7 +20,7 @@ public class TemplateGlobalTest { .withApplicationRoot(root -> root .addClasses(Globals.class, NextGlobals.class) .addAsResource(new StringAsset( - "Hello {currentUser}! Your name is {_name}. You're {age} years old."), + "Hello {currentUser}|{global:currentUser}! Your name is {_name}|{global:_name}. You're {age}|{global:age} years old."), "templates/hello.txt")); @Inject @@ -28,15 +28,19 @@ public class TemplateGlobalTest { @Test public void testTemplateData() { - assertEquals("Hello Fu! Your name is Lu. You're 40 years old.", hello.render()); - assertEquals("Hello Fu! Your name is Lu. You're 40 years old.", - Qute.fmt("Hello {currentUser}! Your name is {_name}. You're {age} years old.").render()); + assertEquals("Hello Fu|Fu! Your name is Lu|Lu. You're 40|40 years old.", hello.render()); + assertEquals("Hello Fu|Fu! Your name is Lu|Lu. You're 40|40 years old.", + Qute.fmt( + "Hello {currentUser}|{global:currentUser}! Your name is {_name}|{global:_name}. You're {age}|{global:age} years old.") + .render()); Globals.user = "Hu"; - assertEquals("Hello Hu! Your name is Lu. You're 20 years old.", hello.render()); - assertEquals("Hello Hu! Your name is Lu. You're 20 years old.", - Qute.fmt("Hello {currentUser}! Your name is {_name}. You're {age} years old.").render()); + assertEquals("Hello Hu|Hu! Your name is Lu|Lu. You're 20|20 years old.", hello.render()); + assertEquals("Hello Hu|Hu! Your name is Lu|Lu. You're 20|20 years old.", + Qute.fmt( + "Hello {currentUser}|{global:currentUser}! Your name is {_name}|{global:_name}. You're {age}|{global:age} years old.") + .render()); - assertEquals("First color is: RED", Qute.fmt("First color is: {colors[0]}").render()); + assertEquals("First color is: RED|RED", Qute.fmt("First color is: {colors[0]}|{global:colors[0]}").render()); } public static class Globals { diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java index 9bcf522449dc6..48a5360c3e92e 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java @@ -46,6 +46,7 @@ import io.quarkus.qute.Results; import io.quarkus.qute.SectionHelperFactory; import io.quarkus.qute.Template; +import io.quarkus.qute.TemplateGlobalProvider; import io.quarkus.qute.TemplateInstance; import io.quarkus.qute.TemplateInstance.Initializer; import io.quarkus.qute.TemplateLocator; @@ -204,9 +205,11 @@ public EngineProducer(QuteContext context, QuteConfig config, QuteRuntimeConfig // Add a special parser hook for Qute.fmt() methods builder.addParserHook(new Qute.IndexedArgumentsParserHook()); - // Add template initializers - for (String initializerClass : context.getTemplateInstanceInitializerClasses()) { - builder.addTemplateInstanceInitializer(createInitializer(initializerClass)); + // Add global providers + for (String globalProviderClass : context.getTemplateGlobalProviderClasses()) { + TemplateGlobalProvider provider = createGlobalProvider(globalProviderClass); + builder.addTemplateInstanceInitializer(provider); + builder.addNamespaceResolver(provider); } // Add a special initializer for templates that contain an inject/cdi namespace expressions @@ -313,17 +316,17 @@ private Resolver createResolver(String resolverClassName) { } } - private TemplateInstance.Initializer createInitializer(String initializerClassName) { + private TemplateGlobalProvider createGlobalProvider(String initializerClassName) { try { Class initializerClazz = Thread.currentThread() .getContextClassLoader().loadClass(initializerClassName); - if (TemplateInstance.Initializer.class.isAssignableFrom(initializerClazz)) { - return (TemplateInstance.Initializer) initializerClazz.getDeclaredConstructor().newInstance(); + if (TemplateGlobalProvider.class.isAssignableFrom(initializerClazz)) { + return (TemplateGlobalProvider) initializerClazz.getDeclaredConstructor().newInstance(); } - throw new IllegalStateException("Not an initializer: " + initializerClazz); + throw new IllegalStateException("Not a global provider: " + initializerClazz); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { - throw new IllegalStateException("Unable to create initializer: " + initializerClassName, e); + throw new IllegalStateException("Unable to create global provider: " + initializerClassName, e); } } diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java index 17a2caee2ddba..0fff3270c5735 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java @@ -12,7 +12,7 @@ public class QuteRecorder { public Supplier createContext(List resolverClasses, List templatePaths, List tags, Map> variants, - List templateInstanceInitializerClasses, Set templateRoots) { + List templateGlobalProviderClasses, Set templateRoots) { return new Supplier() { @Override @@ -40,8 +40,8 @@ public Map> getVariants() { } @Override - public List getTemplateInstanceInitializerClasses() { - return templateInstanceInitializerClasses; + public List getTemplateGlobalProviderClasses() { + return templateGlobalProviderClasses; } @Override @@ -63,7 +63,7 @@ public interface QuteContext { Map> getVariants(); - List getTemplateInstanceInitializerClasses(); + List getTemplateGlobalProviderClasses(); Set getTemplateRoots(); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateGlobalProvider.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateGlobalProvider.java new file mode 100644 index 0000000000000..7918f914ac5c9 --- /dev/null +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateGlobalProvider.java @@ -0,0 +1,10 @@ +package io.quarkus.qute; + +/** + * An implementation is generated for each class declaring a template global. + * + * @see TemplateGlobal + */ +public interface TemplateGlobalProvider extends TemplateInstance.Initializer, NamespaceResolver { + +} diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/AbstractGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/AbstractGenerator.java new file mode 100644 index 0000000000000..84475ab5f42a5 --- /dev/null +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/AbstractGenerator.java @@ -0,0 +1,180 @@ +package io.quarkus.qute.generator; + +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; + +import java.util.HashSet; +import java.util.Set; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.PrimitiveType.Primitive; +import org.jboss.jandex.Type; + +import io.quarkus.gizmo.BranchResult; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.Gizmo; +import io.quarkus.gizmo.IfThenElse; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.qute.CompletedStage; + +public abstract class AbstractGenerator { + + protected final Set generatedTypes; + protected final IndexView index; + protected final ClassOutput classOutput; + + protected AbstractGenerator(IndexView index, ClassOutput classOutput) { + this.generatedTypes = new HashSet<>(); + this.index = index; + this.classOutput = classOutput; + } + + public Set getGeneratedTypes() { + return generatedTypes; + } + + protected void completeBoolean(BytecodeCreator bc, ResultHandle result) { + BranchResult isTrue = bc.ifTrue(result); + BytecodeCreator trueBranch = isTrue.trueBranch(); + trueBranch.returnValue(trueBranch.readStaticField(Descriptors.RESULTS_TRUE)); + BytecodeCreator falseBranch = isTrue.falseBranch(); + falseBranch.returnValue(falseBranch.readStaticField(Descriptors.RESULTS_FALSE)); + } + + protected boolean isEnum(Type returnType) { + if (returnType.kind() != org.jboss.jandex.Type.Kind.CLASS) { + return false; + } + ClassInfo maybeEnum = index.getClassByName(returnType.name()); + return maybeEnum != null && maybeEnum.isEnum(); + } + + protected boolean hasCompletionStage(Type type) { + return !skipMemberType(type) && hasCompletionStageInTypeClosure(index.getClassByName(type.name()), index); + } + + protected boolean hasCompletionStageInTypeClosure(ClassInfo classInfo, + IndexView index) { + return hasClassInTypeClosure(classInfo, DotNames.COMPLETION_STAGE, index); + } + + protected boolean hasClassInTypeClosure(ClassInfo classInfo, DotName className, + IndexView index) { + + if (classInfo == null) { + // TODO cannot perform analysis + return false; + } + if (classInfo.name().equals(className)) { + return true; + } + // Interfaces + for (Type interfaceType : classInfo.interfaceTypes()) { + ClassInfo interfaceClassInfo = index.getClassByName(interfaceType.name()); + if (interfaceClassInfo != null && hasCompletionStageInTypeClosure(interfaceClassInfo, index)) { + return true; + } + } + // Superclass + if (classInfo.superClassType() != null) { + ClassInfo superClassInfo = index.getClassByName(classInfo.superName()); + if (superClassInfo != null && hasClassInTypeClosure(superClassInfo, className, index)) { + return true; + } + } + return false; + } + + protected void processReturnVal(BytecodeCreator bc, Type type, ResultHandle ret, ClassCreator classCreator) { + if (hasCompletionStage(type)) { + bc.returnValue(ret); + } else { + // Try to use some shared CompletedStage constants + if (type.kind() == org.jboss.jandex.Type.Kind.PRIMITIVE + && type.asPrimitiveType().primitive() == Primitive.BOOLEAN) { + completeBoolean(bc, ret); + } else if (type.name().equals(DotNames.BOOLEAN)) { + BytecodeCreator isNull = bc.ifNull(ret).trueBranch(); + isNull.returnValue(isNull.readStaticField(Descriptors.RESULTS_NULL)); + completeBoolean(bc, bc.invokeVirtualMethod(Descriptors.BOOLEAN_VALUE, ret)); + } else if (isEnum(type)) { + BytecodeCreator isNull = bc.ifNull(ret).trueBranch(); + isNull.returnValue(isNull.readStaticField(Descriptors.RESULTS_NULL)); + completeEnum(index.getClassByName(type.name()), classCreator, ret, bc); + } else { + bc.returnValue(bc.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, ret)); + } + } + } + + protected boolean completeEnum(ClassInfo enumClass, ClassCreator valueResolver, ResultHandle result, BytecodeCreator bc) { + IfThenElse ifThenElse = null; + for (FieldInfo enumConstant : enumClass.enumConstants()) { + String name = enumClass.name().toString().replace(".", "_") + "$$" + + enumConstant.name(); + FieldDescriptor enumConstantField = FieldDescriptor.of(enumClass.name().toString(), + enumConstant.name(), enumClass.name().toString()); + + // Additional methods and fields are generated for enums that are part of the index + // We don't care about visibility and atomicity here + // private CompletedStage org_acme_MyEnum$$CONSTANT; + FieldDescriptor csField = valueResolver + .getFieldCreator(name, CompletedStage.class).setModifiers(ACC_PRIVATE) + .getFieldDescriptor(); + // private CompletedStage org_acme_MyEnum$$CONSTANT() { + // if (org_acme_MyEnum$$CONSTANT == null) { + // org_acme_MyEnum$$CONSTANT = CompletedStage.of(MyEnum.CONSTANT); + // } + // return org_acme_MyEnum$$CONSTANT; + // } + MethodCreator enumConstantMethod = valueResolver.getMethodCreator(name, + CompletedStage.class).setModifiers(ACC_PRIVATE); + BytecodeCreator isNull = enumConstantMethod.ifNull(enumConstantMethod + .readInstanceField(csField, enumConstantMethod.getThis())) + .trueBranch(); + ResultHandle val = isNull.readStaticField(enumConstantField); + isNull.writeInstanceField(csField, enumConstantMethod.getThis(), + isNull.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, val)); + enumConstantMethod.returnValue(enumConstantMethod + .readInstanceField(csField, enumConstantMethod.getThis())); + + // Unfortunately, we can't use the BytecodeCreator#enumSwitch() here because the enum class is not loaded + // if(val.equals(MyEnum.CONSTANT)) + // return org_acme_MyEnum$$CONSTANT(); + BytecodeCreator match; + if (ifThenElse == null) { + ifThenElse = bc.ifThenElse( + Gizmo.equals(bc, bc.readStaticField(enumConstantField), result)); + match = ifThenElse.then(); + } else { + match = ifThenElse.elseIf( + b -> Gizmo.equals(b, b.readStaticField(enumConstantField), result)); + } + match.returnValue(match.invokeVirtualMethod( + enumConstantMethod.getMethodDescriptor(), match.getThis())); + } + return true; + } + + protected boolean skipMemberType(Type type) { + switch (type.kind()) { + case VOID: + case PRIMITIVE: + case ARRAY: + case TYPE_VARIABLE: + case UNRESOLVED_TYPE_VARIABLE: + case TYPE_VARIABLE_REFERENCE: + case WILDCARD_TYPE: + return true; + default: + return false; + } + } + +} diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java index a9448bf545ef3..1eea9f6d65045 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -60,7 +59,7 @@ * @see ValueResolver * @see NamespaceResolver */ -public class ExtensionMethodGenerator { +public class ExtensionMethodGenerator extends AbstractGenerator { public static final DotName TEMPLATE_EXTENSION = DotName.createSimple(TemplateExtension.class.getName()); public static final DotName TEMPLATE_ATTRIBUTE = DotName.createSimple(TemplateExtension.TemplateAttribute.class.getName()); @@ -74,14 +73,8 @@ public class ExtensionMethodGenerator { public static final String NAMESPACE = "namespace"; public static final String PATTERN = "pattern"; - private final Set generatedTypes; - private final ClassOutput classOutput; - private final IndexView index; - public ExtensionMethodGenerator(IndexView index, ClassOutput classOutput) { - this.index = index; - this.classOutput = classOutput; - this.generatedTypes = new HashSet<>(); + super(index, classOutput); } public Set getGeneratedTypes() { @@ -235,8 +228,7 @@ private void implementResolve(ClassCreator valueResolver, ClassInfo declaringCla ResultHandle evalContext = resolve.getMethodParam(0); ResultHandle base = resolve.invokeInterfaceMethod(Descriptors.GET_BASE, evalContext); boolean isNameParamRequired = patternField != null || !matchNames.isEmpty() || matchName.equals(TemplateExtension.ANY); - boolean returnsCompletionStage = method.returnType().kind() != Kind.PRIMITIVE && ValueResolverGenerator - .hasCompletionStageInTypeClosure(index.getClassByName(method.returnType().name()), index); + boolean returnsCompletionStage = hasCompletionStage(method.returnType()); ResultHandle ret; if (!params.needsEvaluation()) { diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/TemplateGlobalGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/TemplateGlobalGenerator.java index e6a62d5122a37..6d259cb04ee9d 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/TemplateGlobalGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/TemplateGlobalGenerator.java @@ -6,43 +6,50 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import java.lang.reflect.Modifier; -import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type.Kind; +import io.quarkus.gizmo.BytecodeCreator; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.FieldDescriptor; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.Switch.StringSwitch; +import io.quarkus.qute.EvalContext; import io.quarkus.qute.TemplateGlobal; +import io.quarkus.qute.TemplateGlobalProvider; import io.quarkus.qute.TemplateInstance; /** * Generates {@link TemplateInstance.Initializer}s for {@link TemplateGlobal} annotations. */ -public class TemplateGlobalGenerator { +public class TemplateGlobalGenerator extends AbstractGenerator { public static final DotName TEMPLATE_GLOBAL = DotName.createSimple(TemplateGlobal.class.getName()); public static final String NAME = "name"; public static final String SUFFIX = "_Globals"; - private final Set generatedTypes; - private final ClassOutput classOutput; + private final String namespace; + private int priority; - public TemplateGlobalGenerator(ClassOutput classOutput) { - this.generatedTypes = new HashSet<>(); - this.classOutput = classOutput; + public TemplateGlobalGenerator(ClassOutput classOutput, String namespace, int priority, IndexView index) { + super(index, classOutput); + this.namespace = namespace; + this.priority = priority; } public void generate(ClassInfo declaringClass, Map targets) { @@ -58,10 +65,11 @@ public void generate(ClassInfo declaringClass, Map tar String generatedName = generatedNameFromTarget(targetPackage, baseName, SUFFIX); generatedTypes.add(generatedName.replace('/', '.')); - ClassCreator initializer = ClassCreator.builder().classOutput(classOutput).className(generatedName) - .interfaces(TemplateInstance.Initializer.class).build(); + ClassCreator provider = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .interfaces(TemplateGlobalProvider.class).build(); - MethodCreator accept = initializer.getMethodCreator("accept", void.class, Object.class) + // TemplateInstance.Initializer#accept() + MethodCreator accept = provider.getMethodCreator("accept", void.class, Object.class) .setModifiers(ACC_PUBLIC); for (Entry entry : targets.entrySet()) { @@ -84,7 +92,48 @@ public void generate(ClassInfo declaringClass, Map tar accept.invokeInterfaceMethod(Descriptors.TEMPLATE_INSTANCE_DATA, accept.getMethodParam(0), name, global); } accept.returnValue(null); - initializer.close(); + + // NamespaceResolver#getNamespace() + MethodCreator getNamespace = provider.getMethodCreator("getNamespace", String.class); + getNamespace.returnValue(getNamespace.load(namespace)); + + // WithPriority#getPriority() + MethodCreator getPriority = provider.getMethodCreator("getPriority", int.class); + // Namespace resolvers for the same namespace may not share the same priority + // So we increase the initial priority for each provider + getPriority.returnValue(getPriority.load(priority++)); + + // Resolver#resolve() + MethodCreator resolve = provider.getMethodCreator("resolve", CompletionStage.class, EvalContext.class) + .setModifiers(ACC_PUBLIC); + ResultHandle evalContext = resolve.getMethodParam(0); + ResultHandle name = resolve.invokeInterfaceMethod(Descriptors.GET_NAME, evalContext); + StringSwitch nameSwitch = resolve.stringSwitch(name); + for (Entry e : targets.entrySet()) { + Consumer readGlobal = new Consumer() { + @Override + public void accept(BytecodeCreator bc) { + switch (e.getValue().kind()) { + case FIELD: + FieldInfo field = e.getValue().asField(); + processReturnVal(bc, field.type(), bc.readStaticField(FieldDescriptor.of(field)), provider); + break; + case METHOD: + MethodInfo method = e.getValue().asMethod(); + processReturnVal(bc, method.returnType(), bc.invokeStaticMethod(MethodDescriptor.of(method)), + provider); + break; + default: + throw new IllegalStateException("Unsupported target: " + e.getValue()); + } + + } + }; + nameSwitch.caseOf(e.getKey(), readGlobal); + } + resolve.returnValue(resolve.invokeStaticMethod(Descriptors.RESULTS_NOT_FOUND_EC, evalContext)); + + provider.close(); } public Set getGeneratedTypes() { diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java index ff869c8a15e7a..f6d13de82b267 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java @@ -1,7 +1,6 @@ package io.quarkus.qute.generator; import static java.util.function.Predicate.not; -import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import java.lang.reflect.Modifier; @@ -34,7 +33,6 @@ import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.PrimitiveType; -import org.jboss.jandex.PrimitiveType.Primitive; import org.jboss.jandex.Type; import org.jboss.logging.Logger; @@ -48,13 +46,11 @@ import io.quarkus.gizmo.FieldDescriptor; import io.quarkus.gizmo.FunctionCreator; import io.quarkus.gizmo.Gizmo; -import io.quarkus.gizmo.IfThenElse; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.gizmo.Switch; import io.quarkus.gizmo.TryBlock; -import io.quarkus.qute.CompletedStage; import io.quarkus.qute.EvalContext; import io.quarkus.qute.EvaluatedParams; import io.quarkus.qute.NamespaceResolver; @@ -66,7 +62,7 @@ * * @see ValueResolver */ -public class ValueResolverGenerator { +public class ValueResolverGenerator extends AbstractGenerator { public static Builder builder() { return new Builder(); @@ -92,9 +88,6 @@ public static Builder builder() { public static final int DEFAULT_PRIORITY = 10; - private final Set generatedTypes; - private final IndexView index; - private final ClassOutput classOutput; private final Map nameToClass; private final Map nameToTemplateData; @@ -103,18 +96,12 @@ public static Builder builder() { ValueResolverGenerator(IndexView index, ClassOutput classOutput, Map nameToClass, Map nameToTemplateData, Function> forceGettersFunction) { - this.generatedTypes = new HashSet<>(); - this.classOutput = classOutput; - this.index = index; + super(index, classOutput); this.nameToClass = new HashMap<>(nameToClass); this.nameToTemplateData = new HashMap<>(nameToTemplateData); this.forceGettersFunction = forceGettersFunction; } - public Set getGeneratedTypes() { - return generatedTypes; - } - /** * Generate value resolvers for all classes added via {@link Builder#addClass(ClassInfo, AnnotationInstance)}. */ @@ -377,33 +364,13 @@ private boolean implementResolve(ClassCreator valueResolver, String clazzName, C @Override public void accept(BytecodeCreator bc) { Type returnType = method.returnType(); - boolean hasCompletionStage = !skipMemberType(returnType) - && hasCompletionStageInTypeClosure(index.getClassByName(method.returnType().name()), index); ResultHandle invokeRet; if (Modifier.isInterface(clazz.flags())) { invokeRet = bc.invokeInterfaceMethod(MethodDescriptor.of(method), base); } else { invokeRet = bc.invokeVirtualMethod(MethodDescriptor.of(method), base); } - if (hasCompletionStage) { - bc.returnValue(invokeRet); - } else { - // Try to use some shared CompletedStage constants - if (returnType.kind() == org.jboss.jandex.Type.Kind.PRIMITIVE - && returnType.asPrimitiveType().primitive() == Primitive.BOOLEAN) { - completeBoolean(bc, invokeRet); - } else if (method.returnType().name().equals(DotNames.BOOLEAN)) { - BytecodeCreator isNull = bc.ifNull(invokeRet).trueBranch(); - isNull.returnValue(isNull.readStaticField(Descriptors.RESULTS_NULL)); - completeBoolean(bc, bc.invokeVirtualMethod(Descriptors.BOOLEAN_VALUE, invokeRet)); - } else if (isEnum(returnType)) { - BytecodeCreator isNull = bc.ifNull(invokeRet).trueBranch(); - isNull.returnValue(isNull.readStaticField(Descriptors.RESULTS_NULL)); - completeEnum(index.getClassByName(returnType.name()), valueResolver, invokeRet, bc); - } else { - bc.returnValue(bc.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, invokeRet)); - } - } + processReturnVal(bc, returnType, invokeRet, valueResolver); } }; nameSwitch.caseOf(matchingNames, invokeMethod); @@ -487,71 +454,6 @@ public void accept(BytecodeCreator bc) { return true; } - private void completeBoolean(BytecodeCreator bc, ResultHandle result) { - BranchResult isTrue = bc.ifTrue(result); - BytecodeCreator trueBranch = isTrue.trueBranch(); - trueBranch.returnValue(trueBranch.readStaticField(Descriptors.RESULTS_TRUE)); - BytecodeCreator falseBranch = isTrue.falseBranch(); - falseBranch.returnValue(falseBranch.readStaticField(Descriptors.RESULTS_FALSE)); - } - - private boolean isEnum(Type returnType) { - if (returnType.kind() != org.jboss.jandex.Type.Kind.CLASS) { - return false; - } - ClassInfo maybeEnum = index.getClassByName(returnType.name()); - return maybeEnum != null && maybeEnum.isEnum(); - } - - private boolean completeEnum(ClassInfo enumClass, ClassCreator valueResolver, ResultHandle result, BytecodeCreator bc) { - IfThenElse ifThenElse = null; - for (FieldInfo enumConstant : enumClass.enumConstants()) { - String name = enumClass.name().toString().replace(".", "_") + "$$" - + enumConstant.name(); - FieldDescriptor enumConstantField = FieldDescriptor.of(enumClass.name().toString(), - enumConstant.name(), enumClass.name().toString()); - - // Additional methods and fields are generated for enums that are part of the index - // We don't care about visibility and atomicity here - // private CompletedStage org_acme_MyEnum$$CONSTANT; - FieldDescriptor csField = valueResolver - .getFieldCreator(name, CompletedStage.class).setModifiers(ACC_PRIVATE) - .getFieldDescriptor(); - // private CompletedStage org_acme_MyEnum$$CONSTANT() { - // if (org_acme_MyEnum$$CONSTANT == null) { - // org_acme_MyEnum$$CONSTANT = CompletedStage.of(MyEnum.CONSTANT); - // } - // return org_acme_MyEnum$$CONSTANT; - // } - MethodCreator enumConstantMethod = valueResolver.getMethodCreator(name, - CompletedStage.class).setModifiers(ACC_PRIVATE); - BytecodeCreator isNull = enumConstantMethod.ifNull(enumConstantMethod - .readInstanceField(csField, enumConstantMethod.getThis())) - .trueBranch(); - ResultHandle val = isNull.readStaticField(enumConstantField); - isNull.writeInstanceField(csField, enumConstantMethod.getThis(), - isNull.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, val)); - enumConstantMethod.returnValue(enumConstantMethod - .readInstanceField(csField, enumConstantMethod.getThis())); - - // Unfortunately, we can't use the BytecodeCreator#enumSwitch() here because the enum class is not loaded - // if(val.equals(MyEnum.CONSTANT)) - // return org_acme_MyEnum$$CONSTANT(); - BytecodeCreator match; - if (ifThenElse == null) { - ifThenElse = bc.ifThenElse( - Gizmo.equals(bc, bc.readStaticField(enumConstantField), result)); - match = ifThenElse.then(); - } else { - match = ifThenElse.elseIf( - b -> Gizmo.equals(b, b.readStaticField(enumConstantField), result)); - } - match.returnValue(match.invokeVirtualMethod( - enumConstantMethod.getMethodDescriptor(), match.getThis())); - } - return true; - } - private boolean implementNamespaceResolve(ClassCreator valueResolver, String clazzName, ClassInfo clazz, Predicate filter) { MethodCreator resolve = valueResolver.getMethodCreator("resolve", CompletionStage.class, EvalContext.class) @@ -611,15 +513,13 @@ private boolean implementNamespaceResolve(ClassCreator valueResolver, String cla try (BytecodeCreator matchScope = createMatchScope(resolve, method.name(), 0, method.returnType(), name, params, paramsCount)) { ResultHandle ret; - boolean hasCompletionStage = !skipMemberType(method.returnType()) - && hasCompletionStageInTypeClosure(index.getClassByName(method.returnType().name()), index); ResultHandle invokeRet; if (Modifier.isInterface(clazz.flags())) { invokeRet = matchScope.invokeStaticInterfaceMethod(MethodDescriptor.of(method)); } else { invokeRet = matchScope.invokeStaticMethod(MethodDescriptor.of(method)); } - if (hasCompletionStage) { + if (hasCompletionStage(method.returnType())) { ret = invokeRet; } else { ret = matchScope.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, invokeRet); @@ -702,11 +602,8 @@ private void matchMethod(MethodInfo method, ClassInfo clazz, MethodCreator resol paramsCount); // Invoke the method - ResultHandle ret; - boolean hasCompletionStage = !skipMemberType(method.returnType()) - && hasCompletionStageInTypeClosure(index.getClassByName(method.returnType().name()), index); // Evaluate the params first - ret = matchScope + ResultHandle ret = matchScope .newInstance(MethodDescriptor.ofConstructor(CompletableFuture.class)); // The CompletionStage upon which we invoke whenComplete() @@ -809,7 +706,7 @@ private void matchMethod(MethodInfo method, ClassInfo clazz, MethodCreator resol } } - if (hasCompletionStage) { + if (hasCompletionStage(method.returnType())) { FunctionCreator invokeWhenCompleteFun = tryCatch.createFunction(BiConsumer.class); tryCatch.invokeInterfaceMethod(Descriptors.CF_WHEN_COMPLETE, invokeRet, invokeWhenCompleteFun.getInstance()); @@ -908,9 +805,6 @@ private void matchMethods(String matchName, int matchParamsCount, Collection initFilters(AnnotationInstance templateData) { Predicate filter = ValueResolverGenerator::defaultFilter; if (templateData != null) { @@ -1332,38 +1211,6 @@ private static boolean noneMethodMatches(List methods, String name) { return true; } - public static boolean hasCompletionStageInTypeClosure(ClassInfo classInfo, - IndexView index) { - return hasClassInTypeClosure(classInfo, DotNames.COMPLETION_STAGE, index); - } - - public static boolean hasClassInTypeClosure(ClassInfo classInfo, DotName className, - IndexView index) { - - if (classInfo == null) { - // TODO cannot perform analysis - return false; - } - if (classInfo.name().equals(className)) { - return true; - } - // Interfaces - for (Type interfaceType : classInfo.interfaceTypes()) { - ClassInfo interfaceClassInfo = index.getClassByName(interfaceType.name()); - if (interfaceClassInfo != null && hasCompletionStageInTypeClosure(interfaceClassInfo, index)) { - return true; - } - } - // Superclass - if (classInfo.superClassType() != null) { - ClassInfo superClassInfo = index.getClassByName(classInfo.superName()); - if (superClassInfo != null && hasClassInTypeClosure(superClassInfo, className, index)) { - return true; - } - } - return false; - } - public static boolean isVarArgs(MethodInfo method) { return (method.flags() & 0x00000080) != 0; }