Skip to content

Commit

Permalink
Merge pull request quarkusio#37263 from mkouba/qute-global-namespace
Browse files Browse the repository at this point in the history
Qute: make the global variables accessible via "global:" namespace
  • Loading branch information
mkouba authored Nov 22, 2023
2 parents cd6f885 + b5d7104 commit 934f8e1
Show file tree
Hide file tree
Showing 13 changed files with 430 additions and 239 deletions.
12 changes: 8 additions & 4 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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 <<typesafe_expressions,parameter declarations>> to all templates and so any expression that references a global variable is validated during build.
Expand All @@ -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.
<<typesafe_templates,Type-safe templates>> override the global variables automatically.
For example, the following definition overrides the global variable supplied by the `Globals#user()` method:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,8 @@ void validateMessageBundleMethodsInTemplates(TemplatesAnalysisBuildItem analysis
List<CheckedTemplateBuildItem> checkedTemplates,
BeanDiscoveryFinishedBuildItem beanDiscovery,
List<TemplateDataBuildItem> templateData,
QuteConfig config) {
QuteConfig config,
List<TemplateGlobalBuildItem> globals) {

IndexView index = beanArchiveIndex.getIndex();
Function<String, String> templateIdToPathFun = new Function<String, String>() {
Expand Down Expand Up @@ -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))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -922,7 +923,8 @@ void validateExpressions(TemplatesAnalysisBuildItem templatesAnalysis,
List<CheckedTemplateBuildItem> checkedTemplates,
List<TemplateDataBuildItem> templateData,
QuteConfig config,
PackageConfig packageConfig) {
PackageConfig packageConfig,
List<TemplateGlobalBuildItem> globals) {

long start = System.nanoTime();

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -1130,7 +1132,8 @@ static MatchResult validateNestedExpressions(QuteConfig config, TemplateAnalysis
Map<String, TemplateDataBuildItem> namespaceTemplateData,
List<TemplateExtensionMethodBuildItem> regularExtensionMethods,
Map<String, List<TemplateExtensionMethodBuildItem>> namespaceToExtensionMethods,
AssignabilityCheck assignabilityCheck) {
AssignabilityCheck assignabilityCheck,
List<TemplateGlobalBuildItem> globals) {

LOGGER.debugf("Validate %s from %s", expression, expression.getOrigin());

Expand All @@ -1140,15 +1143,16 @@ 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);

// ======================
// 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;
}
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -1416,7 +1445,8 @@ private static NamespaceResult processNamespace(Expression expression, MatchResu
Map<String, MatchResult> results, TemplateAnalysis templateAnalysis,
Map<String, TemplateDataBuildItem> namespaceTemplateData, JavaMemberLookupConfig lookupConfig,
Map<String, List<TemplateExtensionMethodBuildItem>> namespaceToExtensionMethods,
Function<String, String> templateIdToPathFun) {
Function<String, String> templateIdToPathFun,
List<TemplateGlobalBuildItem> globals) {
String namespace = expression.getNamespace();
if (namespace == null) {
return NamespaceResult.EMPTY;
Expand All @@ -1426,6 +1456,10 @@ private static NamespaceResult processNamespace(Expression expression, MatchResu
TemplateDataBuildItem templateData = null;
List<TemplateExtensionMethodBuildItem> 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:
Expand Down Expand Up @@ -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;
Expand All @@ -1499,18 +1537,20 @@ private static class NamespaceResult {
private final List<TemplateExtensionMethodBuildItem> extensionMethods;
private final boolean ignoring;
private final JavaMemberLookupConfig lookupConfig;
private final TemplateGlobalBuildItem global;

NamespaceResult(String namespace, ClassInfo rootClazz, TypeInfo dataNamespaceExpTypeInfo,
TemplateDataBuildItem templateData,
List<TemplateExtensionMethodBuildItem> namespaceExtensionMethods, boolean ignoring,
JavaMemberLookupConfig lookupConfig) {
TemplateDataBuildItem templateData, List<TemplateExtensionMethodBuildItem> namespaceExtensionMethods,
boolean ignoring,
JavaMemberLookupConfig lookupConfig, TemplateGlobalBuildItem global) {
this.namespace = namespace;
this.rootClazz = rootClazz;
this.dataNamespaceExpTypeInfo = dataNamespaceExpTypeInfo;
this.templateData = templateData;
this.extensionMethods = namespaceExtensionMethods;
this.ignoring = ignoring;
this.lookupConfig = lookupConfig;
this.global = global;
}

boolean hasExtensionMethods() {
Expand All @@ -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)) {
Expand Down Expand Up @@ -1595,7 +1639,8 @@ private static void validateParametersOfNestedVirtualMethods(QuteConfig config,
Map<String, TemplateDataBuildItem> namespaceTemplateData,
List<TemplateExtensionMethodBuildItem> regularExtensionMethods,
Map<String, List<TemplateExtensionMethodBuildItem>> namespaceExtensionMethods,
AssignabilityCheck assignabilityCheck) {
AssignabilityCheck assignabilityCheck,
List<TemplateGlobalBuildItem> globals) {
for (Expression.Part part : expression.getParts()) {
if (part.isVirtualMethod()) {
for (Expression param : part.asVirtualMethod().getParameters()) {
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -1834,7 +1880,7 @@ void generateValueResolvers(QuteConfig config, BuildProducer<GeneratedClassBuild
CompletedApplicationClassPredicateBuildItem applicationClassPredicate,
BuildProducer<GeneratedValueResolverBuildItem> generatedResolvers,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<GeneratedTemplateInitializerBuildItem> generatedInitializers) {
BuildProducer<TemplateGlobalProviderBuildItem> globalProviders) {

if (!incorrectExpressions.isEmpty()) {
// Skip generation if a validation error occurs
Expand Down Expand Up @@ -2004,7 +2050,7 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
}

if (!templateGlobals.isEmpty()) {
TemplateGlobalGenerator globalGenerator = new TemplateGlobalGenerator(classOutput);
TemplateGlobalGenerator globalGenerator = new TemplateGlobalGenerator(classOutput, GLOBAL_NAMESPACE, -1000, index);

Map<DotName, Map<String, AnnotationTarget>> classToTargets = new HashMap<>();
Map<DotName, List<TemplateGlobalBuildItem>> classToGlobals = templateGlobals.stream()
Expand All @@ -2019,7 +2065,7 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
}

for (String generatedType : globalGenerator.getGeneratedTypes()) {
generatedInitializers.produce(new GeneratedTemplateInitializerBuildItem(generatedType));
globalProviders.produce(new TemplateGlobalProviderBuildItem(generatedType));
reflectiveClass.produce(ReflectiveClassBuildItem.builder(generatedType).build());
}
}
Expand Down Expand Up @@ -2399,7 +2445,7 @@ public boolean test(TypeCheck check) {
void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecorder recorder,
List<GeneratedValueResolverBuildItem> generatedValueResolvers, List<TemplatePathBuildItem> templatePaths,
Optional<TemplateVariantsBuildItem> templateVariants,
List<GeneratedTemplateInitializerBuildItem> templateInitializers,
List<TemplateGlobalProviderBuildItem> templateInitializers,
TemplateRootsBuildItem templateRoots) {

List<String> templates = new ArrayList<>();
Expand All @@ -2424,7 +2470,7 @@ void initialize(BuildProducer<SyntheticBeanBuildItem> 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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Loading

0 comments on commit 934f8e1

Please sign in to comment.