diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolver.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolver.java index d3b01b4e15b99..8e7aee9340aad 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolver.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolver.java @@ -21,6 +21,14 @@ default boolean appliesTo(EvalContext context) { return true; } + /** + * + * @return a new builder + */ + static ValueResolverBuilder builder() { + return new ValueResolverBuilder(); + } + // Utility methods static boolean matchClass(EvalContext ctx, Class clazz) { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolverBuilder.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolverBuilder.java new file mode 100644 index 0000000000000..e1699be578428 --- /dev/null +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolverBuilder.java @@ -0,0 +1,185 @@ +package io.quarkus.qute; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Builder for {@link ValueResolver}. + */ +public final class ValueResolverBuilder { + + private int priority; + private Predicate appliesTo; + private Function> resolve; + + ValueResolverBuilder() { + priority = ValueResolver.DEFAULT_PRIORITY; + } + + public ValueResolverBuilder priority(int value) { + this.priority = value; + return this; + } + + /** + * And applies to a part of an expression where the base class is assignable to the specified class. + *

+ * The {@link ValueResolver#appliesTo(EvalContext)} logic defined earlier is replaced with a composite predicate. + * + * @param name + * @return self + */ + public ValueResolverBuilder applyToBaseClass(Class baseClass) { + Predicate p = new Predicate() { + + @Override + public boolean test(EvalContext ec) { + return ValueResolver.matchClass(ec, baseClass); + } + }; + if (appliesTo != null) { + appliesTo = appliesTo.and(p); + } else { + appliesTo = p; + } + return this; + } + + /** + * And applies to a part of an expression where the name is equal to the specified value. + *

+ * The {@link ValueResolver#appliesTo(EvalContext)} logic defined earlier is replaced with a composite predicate. + * + * @param name + * @return self + */ + public ValueResolverBuilder applyToName(String name) { + Predicate p = new Predicate() { + + @Override + public boolean test(EvalContext ec) { + return ec.getName().equals(name); + } + }; + if (appliesTo != null) { + appliesTo = appliesTo.and(p); + } else { + appliesTo = p; + } + return this; + } + + /** + * And applies to a part of an expression where the number of parameters is equal to zero. + *

+ * The {@link ValueResolver#appliesTo(EvalContext)} logic defined earlier is replaced with a composite predicate. + * + * @return self + */ + public ValueResolverBuilder applyToNoParameters() { + Predicate p = new Predicate() { + + @Override + public boolean test(EvalContext ec) { + return ec.getParams().size() == 0; + } + }; + if (appliesTo != null) { + appliesTo = appliesTo.and(p); + } else { + appliesTo = p; + } + return this; + } + + /** + * And applies to a part of an expression where the number of parameters is equal to the specified size. + *

+ * The {@link ValueResolver#appliesTo(EvalContext)} logic defined earlier is replaced with a composite predicate. + * + * @param size + * @return self + */ + public ValueResolverBuilder applyToParameters(int size) { + Predicate p = new Predicate() { + + @Override + public boolean test(EvalContext ec) { + return ec.getParams().size() == size; + } + }; + if (appliesTo != null) { + appliesTo = appliesTo.and(p); + } else { + appliesTo = p; + } + return this; + } + + /** + * The {@link ValueResolver#appliesTo(EvalContext)} logic defined earlier is replaced with the specified predicate. + * + * @param predicate + * @return self + */ + public ValueResolverBuilder appliesTo(Predicate predicate) { + this.appliesTo = predicate; + return this; + } + + public ValueResolverBuilder resolveSync(Function fun) { + this.resolve = new Function>() { + @Override + public CompletionStage apply(EvalContext context) { + return CompletableFuture.completedFuture(fun.apply(context)); + } + }; + return this; + } + + public ValueResolverBuilder resolveAsync(Function> fun) { + this.resolve = fun; + return this; + } + + public ValueResolverBuilder resolveWith(Object value) { + return resolveAsync(ec -> CompletableFuture.completedFuture(value)); + } + + public ValueResolver build() { + return new ValueResolverImpl(priority, appliesTo, resolve); + } + + private static final class ValueResolverImpl implements ValueResolver { + + private final int priority; + private final Predicate appliesTo; + private final Function> resolve; + + public ValueResolverImpl(int priority, Predicate appliesTo, + Function> resolve) { + this.priority = priority; + this.appliesTo = appliesTo; + this.resolve = resolve; + } + + @Override + public int getPriority() { + return priority; + } + + @Override + public boolean appliesTo(EvalContext context) { + return appliesTo != null ? appliesTo.test(context) : true; + } + + @Override + public CompletionStage resolve(EvalContext context) { + return resolve.apply(context); + } + + } + +} diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ValueResolverBuilderTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ValueResolverBuilderTest.java new file mode 100644 index 0000000000000..7c40f1b522237 --- /dev/null +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ValueResolverBuilderTest.java @@ -0,0 +1,62 @@ +package io.quarkus.qute; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.Test; + +public class ValueResolverBuilderTest { + + @Test + public void testBuilder() { + Engine engine = Engine.builder().addDefaults() + .addValueResolver(ValueResolver.builder() + .appliesTo(ec -> ec.getName().equals("foo")) + .resolveSync(ec -> "bar") + .build()) + .addValueResolver(ValueResolver.builder() + .applyToName("name") + .resolveWith("Spok") + .build()) + .addValueResolver(ValueResolver.builder() + .applyToName("age") + .resolveWith(1) + .priority(1) + .build()) + .addValueResolver(ValueResolver.builder() + .applyToName("age") + .resolveWith(10) + .priority(10) + .build()) + .addValueResolver(ValueResolver.builder() + .applyToBaseClass(String.class) + .applyToName("reverse") + .resolveSync(ec -> new StringBuilder(ec.getBase().toString()).reverse()) + .build()) + .addValueResolver(ValueResolver.builder() + .applyToBaseClass(String.class) + .applyToName("upper") + .applyToNoParameters() + .resolveAsync(ec -> CompletableFuture.completedFuture(ec.getBase().toString().toUpperCase())) + .build()) + .addValueResolver(ValueResolver.builder() + .applyToBaseClass(String.class) + .applyToName("upper") + .applyToParameters(1) + .resolveAsync(ec -> { + return ec.evaluate(ec.getParams().get(0)).thenApply(r -> { + return ec.getBase().toString().substring((int) r).toUpperCase(); + }); + }) + .build()) + .build(); + + assertEquals("bar", engine.parse("{foo}").render()); + assertEquals("Spok", engine.parse("{name}").render()); + assertEquals("10", engine.parse("{age}").render()); + assertEquals("zab", engine.parse("{val.reverse}").data("val", "baz").render()); + assertEquals("BAZ", engine.parse("{val.upper}").data("val", "baz").render()); + assertEquals("AZ", engine.parse("{val.upper(1)}").data("val", "baz").render()); + } + +}