From ace24ab1910abdd326ae70dbe82453382f1bb2a3 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 26 Mar 2021 14:00:32 +0100 Subject: [PATCH] Qute - minor cleanup and micro optimizations - it's mostly nitpicking, an attempt to fix a small perf drop observed in microbenchmarks since v1.11 --- .../extensions/MapTemplateExtensions.java | 8 +- .../main/java/io/quarkus/qute/EngineImpl.java | 5 +- .../java/io/quarkus/qute/EvaluatedParams.java | 15 ++- .../java/io/quarkus/qute/EvaluatorImpl.java | 38 +++--- .../main/java/io/quarkus/qute/Futures.java | 2 + .../java/io/quarkus/qute/IfSectionHelper.java | 124 +++++++++++------- .../io/quarkus/qute/LoopSectionHelper.java | 17 ++- .../java/io/quarkus/qute/MultiResultNode.java | 3 +- .../src/main/java/io/quarkus/qute/Parser.java | 2 +- .../io/quarkus/qute/ResolutionContext.java | 7 - .../quarkus/qute/ResolutionContextImpl.java | 21 +-- .../main/java/io/quarkus/qute/Results.java | 1 + .../java/io/quarkus/qute/SectionNode.java | 13 +- .../java/io/quarkus/qute/TemplateImpl.java | 15 ++- .../io/quarkus/qute/TemplateInstanceBase.java | 2 +- .../java/io/quarkus/qute/ValueResolvers.java | 31 ++--- .../java/io/quarkus/qute/AsyncDataTest.java | 34 +++++ .../test/java/io/quarkus/qute/MutinyTest.java | 4 +- 18 files changed, 209 insertions(+), 133 deletions(-) create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/AsyncDataTest.java diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/extensions/MapTemplateExtensions.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/extensions/MapTemplateExtensions.java index 83afd7ab9725e..7c35d9406a181 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/extensions/MapTemplateExtensions.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/extensions/MapTemplateExtensions.java @@ -12,7 +12,7 @@ @Vetoed // Make sure no bean is created from this class @TemplateExtension public class MapTemplateExtensions { - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({ "rawtypes" }) @TemplateExtension(matchName = ANY) static Object map(Map map, String name) { switch (name) { @@ -29,7 +29,11 @@ static Object map(Map map, String name) { case "isEmpty": return map.isEmpty(); default: - return map.getOrDefault(name, Result.NOT_FOUND); + Object val = map.get(name); + if (val == null) { + return map.containsKey(name) ? null : Result.NOT_FOUND; + } + return val; } } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java index 003ac4b586d6a..6371491e03ec8 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java @@ -41,8 +41,9 @@ class EngineImpl implements Engine { EngineImpl(EngineBuilder builder) { this.sectionHelperFactories = Collections.unmodifiableMap(new HashMap<>(builder.sectionHelperFactories)); this.valueResolvers = sort(builder.valueResolvers); - this.namespaceResolvers = ImmutableList.copyOf(builder.namespaceResolvers); - this.evaluator = new EvaluatorImpl(this.valueResolvers); + this.namespaceResolvers = ImmutableList. builder() + .addAll(builder.namespaceResolvers).add(new TemplateImpl.DataNamespaceResolver()).build(); + this.evaluator = new EvaluatorImpl(this.valueResolvers, this.namespaceResolvers); this.templates = new ConcurrentHashMap<>(); this.locators = sort(builder.locators); this.resultMappers = sort(builder.resultMappers); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatedParams.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatedParams.java index 5cb99d715642d..21c2b6ed47e66 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatedParams.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatedParams.java @@ -32,7 +32,7 @@ public static EvaluatedParams evaluate(EvalContext context) { return new EvaluatedParams(context.evaluate(params.get(0))); } CompletableFuture[] allResults = new CompletableFuture[params.size()]; - List> results = new LinkedList<>(); + List> results = null; int i = 0; Iterator it = params.iterator(); while (it.hasNext()) { @@ -40,10 +40,21 @@ public static EvaluatedParams evaluate(EvalContext context) { CompletableFuture result = context.evaluate(expression).toCompletableFuture(); allResults[i++] = result; if (!expression.isLiteral()) { + if (results == null) { + results = new LinkedList<>(); + } results.add(result); } } - return new EvaluatedParams(CompletableFuture.allOf(results.toArray(new CompletableFuture[0])), allResults); + CompletionStage cs; + if (results == null) { + cs = Futures.COMPLETED; + } else if (results.size() == 1) { + cs = results.get(0); + } else { + cs = CompletableFuture.allOf(results.toArray(new CompletableFuture[0])); + } + return new EvaluatedParams(cs, allResults); } public static EvaluatedParams evaluateMessageKey(EvalContext context) { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java index 434091c667e01..74a30f151cd16 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java @@ -19,9 +19,11 @@ class EvaluatorImpl implements Evaluator { private static final Logger LOGGER = Logger.getLogger(EvaluatorImpl.class); private final List resolvers; + private final List namespaceResolvers; - EvaluatorImpl(List valueResolvers) { + EvaluatorImpl(List valueResolvers, List namespaceResolvers) { this.resolvers = valueResolvers; + this.namespaceResolvers = namespaceResolvers; } @Override @@ -29,7 +31,13 @@ public CompletionStage evaluate(Expression expression, ResolutionContext Iterator parts; if (expression.hasNamespace()) { parts = expression.getParts().iterator(); - NamespaceResolver resolver = findNamespaceResolver(expression.getNamespace(), resolutionContext); + NamespaceResolver resolver = null; + for (NamespaceResolver namespaceResolver : namespaceResolvers) { + if (namespaceResolver.getNamespace().equals(expression.getNamespace())) { + resolver = namespaceResolver; + break; + } + } if (resolver == null) { LOGGER.errorf("No namespace resolver found for: %s", expression.getNamespace()); return Futures.failure(new TemplateException("No resolver for namespace: " + expression.getNamespace())); @@ -53,20 +61,6 @@ public CompletionStage evaluate(Expression expression, ResolutionContext } } - private NamespaceResolver findNamespaceResolver(String namespace, ResolutionContext resolutionContext) { - if (resolutionContext == null) { - return null; - } - if (resolutionContext.getNamespaceResolvers() != null) { - for (NamespaceResolver resolver : resolutionContext.getNamespaceResolvers()) { - if (resolver.getNamespace().equals(namespace)) { - return resolver; - } - } - } - return findNamespaceResolver(namespace, resolutionContext.getParent()); - } - private CompletionStage resolveReference(boolean tryParent, Object ref, Iterator parts, ResolutionContext resolutionContext) { Part part = parts.next(); @@ -138,7 +132,7 @@ private CompletionStage resolve(EvalContextImpl evalContext, Iterator toCompletionStage(Object result) { + private static CompletionStage toCompletionStage(Object result) { if (result instanceof CompletionStage) { // If the result is a completion stage return it as is return (CompletionStage) result; @@ -155,6 +149,8 @@ static class EvalContextImpl implements EvalContext { final Object base; final ResolutionContext resolutionContext; final PartImpl part; + final List params; + final String name; EvalContextImpl(boolean tryParent, Object base, Part part, ResolutionContext resolutionContext) { this(tryParent, base, resolutionContext, part); @@ -165,6 +161,8 @@ static class EvalContextImpl implements EvalContext { this.base = base; this.resolutionContext = resolutionContext; this.part = (PartImpl) part; + this.name = part.getName(); + this.params = part.isVirtualMethod() ? part.asVirtualMethod().getParameters() : Collections.emptyList(); } @Override @@ -174,12 +172,12 @@ public Object getBase() { @Override public String getName() { - return part.getName(); + return name; } @Override public List getParams() { - return part.isVirtualMethod() ? part.asVirtualMethod().getParameters() : Collections.emptyList(); + return params; } @Override @@ -209,7 +207,7 @@ void setCachedResolver(ValueResolver valueResolver) { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("EvalContextImpl [tryParent=").append(tryParent).append(", base=").append(base).append(", name=") - .append(getBase()).append(", params=").append(getParams()).append("]"); + .append(getName()).append(", params=").append(getParams()).append("]"); return builder.toString(); } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Futures.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Futures.java index a012e3d453e98..50b3862fc8852 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Futures.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Futures.java @@ -8,6 +8,8 @@ public final class Futures { + static final CompletableFuture COMPLETED = CompletableFuture.completedFuture(null); + private Futures() { } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java index 4b4d9f742f591..53b146911ed4a 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java @@ -16,6 +16,7 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; /** @@ -152,15 +153,6 @@ interface Condition { Operator getOperator(); - /** - * Short-circuiting evaluation. - * - * @return null if evaluation should continue - */ - default Boolean evaluate(Object value) { - return getOperator() != null ? getOperator().evaluate(value) : null; - } - default boolean isLogicalComplement() { return Operator.NOT.equals(getOperator()); } @@ -169,16 +161,32 @@ default boolean isEmpty() { return false; } + default Object getLiteralValue() { + return null; + } + } static class OperandCondition implements Condition { final Operator operator; final Expression expression; + final Object literalValue; OperandCondition(Operator operator, Expression expression) { this.operator = operator; this.expression = expression; + CompletableFuture literalVal = expression.getLiteralValue(); + if (literalVal != null) { + try { + this.literalValue = literalVal.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + } else { + this.literalValue = null; + } + } @Override @@ -191,6 +199,11 @@ public Operator getOperator() { return operator; } + @Override + public Object getLiteralValue() { + return literalValue; + } + @Override public String toString() { return "OperandCondition [operator=" + operator + ", expression=" + expression.toOriginalString() + "]"; @@ -213,53 +226,27 @@ public CompletionStage evaluate(SectionResolutionContext context) { return evaluateNext(context, null, conditions.iterator()); } - CompletionStage evaluateNext(SectionResolutionContext context, Object value, Iterator iter) { + static CompletionStage evaluateNext(SectionResolutionContext context, Object value, Iterator iter) { CompletableFuture result = new CompletableFuture<>(); - if (!iter.hasNext()) { - result.complete(value); + Condition next = iter.next(); + Boolean shortResult = null; + Operator operator = next.getOperator(); + if (operator != null && operator.isShortCircuiting()) { + shortResult = operator.evaluate(value); + } + if (shortResult != null) { + // There is no need to continue with the next operand + result.complete(shortResult); } else { - Condition next = iter.next(); - Boolean shortResult = null; - Operator operator = next.getOperator(); - if (operator != null && operator.isShortCircuiting()) { - shortResult = operator.evaluate(value); - } - if (shortResult != null) { - // There is no need to continue with the next operand - result.complete(shortResult); + Object literalVal = next.getLiteralValue(); + if (literalVal != null) { + processConditionValue(context, next, operator, value, literalVal, result, iter); } else { next.evaluate(context).whenComplete((r, t) -> { if (t != null) { result.completeExceptionally(t); } else { - Object val; - if (next.isLogicalComplement()) { - r = Booleans.isFalsy(r) ? Boolean.TRUE : Boolean.FALSE; - } - if (operator == null || !operator.isBinary()) { - val = r; - } else { - try { - if (Result.NOT_FOUND.equals(r)) { - r = null; - } - Object localValue = value; - if (Result.NOT_FOUND.equals(localValue)) { - localValue = null; - } - val = operator.evaluate(localValue, r); - } catch (Throwable e) { - result.completeExceptionally(e); - throw e; - } - } - evaluateNext(context, val, iter).whenComplete((r2, t2) -> { - if (t2 != null) { - result.completeExceptionally(t2); - } else { - result.complete(r2); - } - }); + processConditionValue(context, next, operator, value, r, result, iter); } }); } @@ -282,6 +269,43 @@ public String toString() { return "CompositeCondition [conditions=" + conditions.size() + ", operator=" + operator + "]"; } + static void processConditionValue(SectionResolutionContext context, Condition condition, Operator operator, + Object previousValue, Object conditionValue, CompletableFuture result, Iterator iter) { + Object val; + if (condition.isLogicalComplement()) { + conditionValue = Booleans.isFalsy(conditionValue) ? Boolean.TRUE : Boolean.FALSE; + } + if (operator == null || !operator.isBinary()) { + val = conditionValue; + } else { + // Binary operator + try { + if (Result.NOT_FOUND.equals(conditionValue)) { + conditionValue = null; + } + Object localValue = previousValue; + if (Result.NOT_FOUND.equals(localValue)) { + localValue = null; + } + val = operator.evaluate(localValue, conditionValue); + } catch (Throwable e) { + result.completeExceptionally(e); + throw e; + } + } + if (!iter.hasNext()) { + result.complete(val); + } else { + evaluateNext(context, val, iter).whenComplete((r2, t2) -> { + if (t2 != null) { + result.completeExceptionally(t2); + } else { + result.complete(r2); + } + }); + } + } + } enum Operator { @@ -544,7 +568,7 @@ static Condition createCondition(Object param, SectionBlock block, Operator oper nextOperator = null; } } - condition = new CompositeCondition(operator, conditions); + condition = new CompositeCondition(operator, ImmutableList.copyOf(conditions)); } else { throw new TemplateException("Unsupported param type: " + param); } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java index cf98633423625..45056d432eebc 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java @@ -5,6 +5,7 @@ import io.quarkus.qute.Results.Result; import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -38,7 +39,8 @@ public CompletionStage resolve(SectionResolutionContext context) { "Loop error in template [%s] on line %s: {%s} resolved to null, use {%> results = new ArrayList<>(); + // Try to extract the capacity for collections, maps and arrays to avoid resize + List> results = new ArrayList<>(extractSize(it)); Iterator iterator = extractIterator(it); int idx = 0; // Ideally, we should not block here but we still need to retain the order of results @@ -70,6 +72,19 @@ public CompletionStage resolve(SectionResolutionContext context) { }); } + private static int extractSize(Object it) { + if (it instanceof Collection) { + return ((Collection) it).size(); + } else if (it instanceof Map) { + return ((Map) it).size(); + } else if (it.getClass().isArray()) { + return Array.getLength(it); + } else if (it instanceof Integer) { + return ((Integer) it); + } + return 10; + } + private Iterator extractIterator(Object it) { if (it instanceof Iterable) { return ((Iterable) it).iterator(); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/MultiResultNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/MultiResultNode.java index 03978ae487ed9..8f6124cb0d481 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/MultiResultNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/MultiResultNode.java @@ -12,7 +12,7 @@ public class MultiResultNode implements ResultNode { private final ResultNode[] results; public MultiResultNode(CompletableFuture[] futures) { - results = new ResultNode[futures.length]; + ResultNode[] results = new ResultNode[futures.length]; for (int i = 0; i < futures.length; i++) { try { results[i] = futures[i].get(); @@ -20,6 +20,7 @@ public MultiResultNode(CompletableFuture[] futures) { throw new IllegalStateException(e); } } + this.results = results; } @Override diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java index b4e6e80b91247..5f281a40f38ff 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java @@ -738,7 +738,7 @@ static ExpressionImpl parseExpression(Supplier idGenerator, String valu } parts.add(part); } - return new ExpressionImpl(idGenerator.get(), namespace, parts, Result.NOT_FOUND, origin); + return new ExpressionImpl(idGenerator.get(), namespace, ImmutableList.copyOf(parts), Result.NOT_FOUND, origin); } private static Part createPart(Supplier idGenerator, String namespace, Part first, diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContext.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContext.java index f5247926e6fde..0b28529b6c787 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContext.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContext.java @@ -1,6 +1,5 @@ package io.quarkus.qute; -import java.util.List; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -40,12 +39,6 @@ public interface ResolutionContext { */ Object getData(); - /** - * - * @return the namespace resolvers - */ - List getNamespaceResolvers(); - /** * * @return the parent context or null diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContextImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContextImpl.java index 6543cfee24138..2bdc5915a1b0e 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContextImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContextImpl.java @@ -1,21 +1,18 @@ package io.quarkus.qute; -import java.util.List; import java.util.Map; import java.util.concurrent.CompletionStage; class ResolutionContextImpl implements ResolutionContext { private final Object data; - private final List namespaceResolvers; private final Evaluator evaluator; private final Map extendingBlocks; private final TemplateInstance templateInstance; - ResolutionContextImpl(Object data, List namespaceResolvers, + ResolutionContextImpl(Object data, Evaluator evaluator, Map extendingBlocks, TemplateInstance templateInstance) { this.data = data; - this.namespaceResolvers = namespaceResolvers; this.evaluator = evaluator; this.extendingBlocks = extendingBlocks; this.templateInstance = templateInstance; @@ -41,11 +38,6 @@ public Object getData() { return data; } - @Override - public List getNamespaceResolvers() { - return namespaceResolvers; - } - @Override public ResolutionContextImpl getParent() { return null; @@ -74,11 +66,13 @@ static class ChildResolutionContext implements ResolutionContext { private final ResolutionContext parent; private final Object data; private final Map extendingBlocks; + private final Evaluator evaluator; public ChildResolutionContext(ResolutionContext parent, Object data, Map extendingBlocks) { this.parent = parent; this.data = data; this.extendingBlocks = extendingBlocks; + this.evaluator = parent.getEvaluator(); } @Override @@ -89,7 +83,7 @@ public CompletionStage evaluate(String expression) { @Override public CompletionStage evaluate(Expression expression) { // Make sure we use the correct resolution context - return getEvaluator().evaluate(expression, this); + return evaluator.evaluate(expression, this); } @Override @@ -102,11 +96,6 @@ public Object getData() { return data; } - @Override - public List getNamespaceResolvers() { - return parent.getNamespaceResolvers(); - } - @Override public ResolutionContext getParent() { return parent; @@ -133,7 +122,7 @@ public Object getAttribute(String key) { @Override public Evaluator getEvaluator() { - return parent.getEvaluator(); + return evaluator; } } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Results.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Results.java index f6feae2b8a7d3..7578190c4a83b 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Results.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Results.java @@ -11,6 +11,7 @@ public final class Results { public static final CompletionStage NOT_FOUND = CompletableFuture.completedFuture(Result.NOT_FOUND); public static final CompletableFuture FALSE = CompletableFuture.completedFuture(false); public static final CompletableFuture TRUE = CompletableFuture.completedFuture(true); + public static final CompletableFuture NULL = CompletableFuture.completedFuture(null); private Results() { } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java index e0fef18d51cb4..2fff6e5ef3dfa 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java @@ -147,22 +147,31 @@ public CompletionStage execute(SectionBlock block, ResolutionContext } int size = block.nodes.size(); if (size == 1) { + // Single node in the block return block.nodes.get(0).resolve(context); } CompletableFuture result = new CompletableFuture(); + + // Collect async results first @SuppressWarnings("unchecked") CompletableFuture[] allResults = new CompletableFuture[size]; - List> asyncResults = new LinkedList<>(); + List> asyncResults = null; int idx = 0; for (TemplateNode node : block.nodes) { CompletableFuture nodeResult = node.resolve(context).toCompletableFuture(); allResults[idx++] = nodeResult; if (node.isConstant()) { + // Constant blocks do not need to be resolved continue; } + if (asyncResults == null) { + asyncResults = new LinkedList<>(); + } asyncResults.add(nodeResult); } - if (asyncResults.isEmpty()) { + + if (asyncResults == null) { + // No async results present result.complete(new MultiResultNode(allResults)); } else { CompletionStage cs; diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java index f20cab52b3330..8034750a14db3 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java @@ -97,12 +97,9 @@ public CompletionStage consume(Consumer resultConsumer) { private CompletionStage renderData(Object data, Consumer consumer) { CompletableFuture result = new CompletableFuture<>(); - DataNamespaceResolver dataResolver = new DataNamespaceResolver(); - List namespaceResolvers = ImmutableList. builder() - .addAll(engine.getNamespaceResolvers()).add(dataResolver).build(); - ResolutionContext rootContext = new ResolutionContextImpl(data, namespaceResolvers, + ResolutionContext rootContext = new ResolutionContextImpl(data, engine.getEvaluator(), null, this); - dataResolver.rootContext = rootContext; + setAttribute(DataNamespaceResolver.ROOT_CONTEXT, rootContext); // Async resolution root.resolve(rootContext).whenComplete((r, t) -> { if (t != null) { @@ -124,11 +121,15 @@ private CompletionStage renderData(Object data, Consumer consumer) static class DataNamespaceResolver implements NamespaceResolver { - ResolutionContext rootContext; + static final String ROOT_CONTEXT = "qute$rootContext"; @Override public CompletionStage resolve(EvalContext context) { - return rootContext.evaluate(context.getName()); + Object rootContext = context.getAttribute(ROOT_CONTEXT); + if (rootContext != null && rootContext instanceof ResolutionContext) { + return ((ResolutionContext) rootContext).evaluate(context.getName()); + } + return Results.NOT_FOUND; } @Override diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateInstanceBase.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateInstanceBase.java index c9675bde7c85b..5be75b42faa2d 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateInstanceBase.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateInstanceBase.java @@ -7,7 +7,7 @@ public abstract class TemplateInstanceBase implements TemplateInstance { protected Object data; protected Map dataMap; - protected Map attributes; + protected final Map attributes; public TemplateInstanceBase() { this.attributes = new HashMap<>(); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java index fefd93a678e6f..74b20c85cdb90 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java @@ -90,14 +90,8 @@ public boolean appliesTo(EvalContext context) { if (context.getParams().size() != 1) { return false; } - switch (context.getName()) { - case "?:": - case "or": - case ":": - return true; - default: - return false; - } + String name = context.getName(); + return name.equals("?:") || name.equals("or") || name.equals(":"); } @Override @@ -146,13 +140,8 @@ public boolean appliesTo(EvalContext context) { if (context.getParams().size() != 1) { return false; } - switch (context.getName()) { - case "?": - case "ifTruthy": - return true; - default: - return false; - } + String name = context.getName(); + return name.equals("?") || name.equals("ifTruthy"); } @Override @@ -389,7 +378,8 @@ private static Object entryResolve(Entry entry, String name) { @SuppressWarnings("rawtypes") private static CompletionStage mapResolveAsync(EvalContext context) { Map map = (Map) context.getBase(); - switch (context.getName()) { + String name = context.getName(); + switch (name) { case "keys": case "keySet": return CompletableFuture.completedFuture(map.keySet()); @@ -401,7 +391,7 @@ private static CompletionStage mapResolveAsync(EvalContext context) { return CompletableFuture.completedFuture(map.size()); case "empty": case "isEmpty": - return CompletableFuture.completedFuture(map.isEmpty()); + return map.isEmpty() ? Results.TRUE : Results.FALSE; case "get": if (context.getParams().size() == 1) { return context.evaluate(context.getParams().get(0)).thenCompose(k -> { @@ -415,8 +405,11 @@ private static CompletionStage mapResolveAsync(EvalContext context) { }); } default: - return map.containsKey(context.getName()) ? CompletableFuture.completedFuture(map.get(context.getName())) - : Results.NOT_FOUND; + Object val = map.get(name); + if (val == null) { + return map.containsKey(name) ? Results.NULL : Results.NOT_FOUND; + } + return CompletableFuture.completedFuture(val); } } diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/AsyncDataTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/AsyncDataTest.java new file mode 100644 index 0000000000000..e4414e51b13dc --- /dev/null +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/AsyncDataTest.java @@ -0,0 +1,34 @@ +package io.quarkus.qute; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.junit.jupiter.api.Test; + +public class AsyncDataTest { + + @Test + public void testAsyncData() { + Engine engine = Engine.builder().addDefaults().addValueResolver(ValueResolver.builder().applyToBaseClass(Client.class) + .applyToName("tokens").resolveSync(ec -> ((Client) ec.getBase()).getTokens()).build()).build(); + assertEquals("alpha:bravo:delta:", + engine.parse("{#for token in client.tokens}{token}:{/for}").data("client", new Client()).render()); + assertEquals("alpha:bravo:delta:", + engine.parse("{#for token in tokens}{token}:{/for}").data("tokens", new Client().getTokens()).render()); + assertEquals("alpha", engine.parse("{token}").data("token", CompletableFuture.completedFuture("alpha")).render()); + } + + static class Client { + + public CompletionStage> getTokens() { + CompletableFuture> tokens = new CompletableFuture<>(); + tokens.complete(Arrays.asList("alpha", "bravo", "delta")); + return tokens; + } + + } + +} diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/MutinyTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/MutinyTest.java index 145fb3d64733d..6512860b8e5ca 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/MutinyTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/MutinyTest.java @@ -24,8 +24,8 @@ public void testCreateMulti() throws InterruptedException { Multi multi = template.data(data).createMulti(); assertMulti(multi, "foofooalpha"); - assertMulti(multi.transform().byDroppingDuplicates(), "fooalpha"); - assertMulti(multi.transform().byTakingFirstItems(1), "foo"); + assertMulti(multi.select().distinct(), "fooalpha"); + assertMulti(multi.select().first(), "foo"); } @Test