From 9de57efdb30e353dea9c6cf2ee2c3bef6ee333b6 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 21 Feb 2020 15:17:10 +0100 Subject: [PATCH] Qute - add some javadoc to public API - minor optimization in EvaluatorImpl - added float and double literal support + tests --- .../src/main/java/io/quarkus/qute/Engine.java | 48 +++++++++++++----- .../java/io/quarkus/qute/EngineBuilder.java | 6 +++ .../java/io/quarkus/qute/EvalContext.java | 16 +++++- .../java/io/quarkus/qute/EvaluatorImpl.java | 23 ++++++--- .../main/java/io/quarkus/qute/Expression.java | 6 +++ .../java/io/quarkus/qute/LiteralSupport.java | 50 +++++++++++++------ .../src/main/java/io/quarkus/qute/Mapper.java | 6 +++ .../java/io/quarkus/qute/MultiResultNode.java | 3 ++ .../io/quarkus/qute/NamespaceResolver.java | 18 ++++--- .../src/main/java/io/quarkus/qute/Parser.java | 6 +-- .../io/quarkus/qute/ResolutionContext.java | 10 ++-- .../main/java/io/quarkus/qute/Resolver.java | 9 +++- .../main/java/io/quarkus/qute/Results.java | 8 ++- .../java/io/quarkus/qute/SectionHelper.java | 2 + .../io/quarkus/qute/SectionHelperFactory.java | 13 +++-- .../io/quarkus/qute/SingleResultNode.java | 2 +- .../main/java/io/quarkus/qute/Template.java | 12 ++++- .../java/io/quarkus/qute/TemplateData.java | 8 ++- .../java/io/quarkus/qute/TemplateLocator.java | 3 +- .../java/io/quarkus/qute/ValueResolver.java | 9 +++- .../io/quarkus/qute/EngineBuilderTest.java | 22 ++++++++ .../io/quarkus/qute/LiteralSupportTest.java | 40 +++++++++++++++ 22 files changed, 258 insertions(+), 62 deletions(-) create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/EngineBuilderTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/LiteralSupportTest.java diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java index a1eeca1d0..b205d0319 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java @@ -5,30 +5,44 @@ import java.util.Map; import java.util.function.Predicate; /** - * Template engine configuration. + * Represents a central point for template management. It has a dedicated configuration and is able to cache the + * template definitions. */ public interface Engine { + /** + * + * @return a new builder instance + */ static EngineBuilder builder() { return new EngineBuilder(); } + /** + * Parse the template contents. + *

+ * Note that this method always returns a new {@link Template} instance. + * + * @param content + * @return the template + * @see Engine#getTemplate(String) + */ default Template parse(String content) { return parse(content, null); } + /** + * Parse the template contents with the specified variant. + *

+ * Note that this method always returns a new {@link Template} instance. + * + * @param content + * @param variant + * @return the template + * @see Engine#getTemplate(String) + */ public Template parse(String content, Variant variant); - public SectionHelperFactory getSectionHelperFactory(String name); - - public Map> getSectionHelperFactories(); - - public List getValueResolvers(); - - public List getNamespaceResolvers(); - - public Evaluator getEvaluator(); - /** * * @return an immutable list of result mappers @@ -44,7 +58,7 @@ public interface Engine { public Template putTemplate(String id, Template template); /** - * Obtain a compiled template for the given id. The template could be registered using + * Obtain a template for the given identifier. The template could be registered using * {@link #putTemplate(String, Template)} or loaded by a template locator. * * @param id @@ -65,4 +79,14 @@ public interface Engine { */ public void removeTemplates(Predicate test); + public SectionHelperFactory getSectionHelperFactory(String name); + + public Map> getSectionHelperFactories(); + + public List getValueResolvers(); + + public List getNamespaceResolvers(); + + public Evaluator getEvaluator(); + } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java index b5cfe59ba..4dbe55c7b 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java @@ -93,6 +93,12 @@ public final class EngineBuilder { } public EngineBuilder addNamespaceResolver(NamespaceResolver resolver) { + for (NamespaceResolver namespaceResolver : namespaceResolvers) { + if (namespaceResolver.getNamespace().equals(resolver.getNamespace())) { + throw new IllegalArgumentException( + String.format("Namespace %s is already handled by %s", resolver.getNamespace())); + } + } this.namespaceResolvers.add(resolver); return this; } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalContext.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalContext.java index 475b1a5ea..6797c76cc 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalContext.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalContext.java @@ -4,7 +4,9 @@ import java.util.List; import java.util.concurrent.CompletionStage; /** - * Each part of an {@link Expression} is evaluated with a new context. + * Evaluation context of a specific part of an {@link Expression}. + * + * @see Expression#parts */ public interface EvalContext { @@ -29,8 +31,20 @@ public interface EvalContext { */ List getParams(); + /** + * Parse and evaluate the given expression using the relevant {@link ResolutionContext} + * + * @param expression + * @return the result + */ CompletionStage evaluate(String expression); + /** + * Evaluate the given expression using the relevant {@link ResolutionContext}. + * + * @param expression + * @return the result + */ CompletionStage evaluate(Expression expression); } \ No newline at end of file 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 6edb2f6f1..8f55ddb9d 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 @@ -66,14 +66,21 @@ class EvaluatorImpl implements Evaluator { private CompletionStage resolveReference(boolean tryParent, Object ref, Iterator parts, ResolutionContext resolutionContext) { - return resolve(new EvalContextImpl(tryParent, ref, parts.next(), resolutionContext), resolvers.iterator()) - .thenCompose(r -> { - if (parts.hasNext()) { - return resolveReference(tryParent, r, parts, resolutionContext); - } else { - return CompletableFuture.completedFuture(r); - } - }); + String part = parts.next(); + EvalContextImpl evalContext = new EvalContextImpl(tryParent, ref, part, resolutionContext); + if (!parts.hasNext()) { + // The last part - no need to compose + return resolve(evalContext, resolvers.iterator()); + } else { + return resolve(evalContext, resolvers.iterator()) + .thenCompose(r -> { + if (parts.hasNext()) { + return resolveReference(tryParent, r, parts, resolutionContext); + } else { + return CompletableFuture.completedFuture(r); + } + }); + } } private CompletionStage resolve(EvalContextImpl evalContext, Iterator resolvers) { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expression.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expression.java index 2caba2aa2..b53d086da 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expression.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expression.java @@ -12,6 +12,12 @@ import java.util.concurrent.ExecutionException; /** * Represents a value expression. It could be a literal such as {@code 'foo'}. It could have a namespace such as {@code data} * for {@code data:name}. It could have several parts such as {@code item} and {@code name} for {@code item.name}. + *

+ * An expression may have a "type check information" attached. The string has a form {@code [TYPE_INFO].foo.baz} + * where TYPE_INFO represent the fully qualified type name (including type parameters) and SECTION_HINT represents an optional + * hint set by the corresponding section helper. For example the expression {@code foo.name} may have the following type check + * info: {@code [org.acme.Foo].name} and the expression {@code it.name} may have + * {@code [java.util.List].name}. * * @see Evaluator */ diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java index dd0de3902..e1695c3cd 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java @@ -8,9 +8,10 @@ class LiteralSupport { private static final Logger LOGGER = Logger.getLogger(LiteralSupport.class); - static final Pattern INTEGER_LITERAL_PATTERN = Pattern.compile("(\\+|-)?\\d{1,10}"); - - static final Pattern LONG_LITERAL_PATTERN = Pattern.compile("(\\+|-)?\\d{1,19}(L|l)"); + static final Pattern INTEGER_LITERAL_PATTERN = Pattern.compile("[-+]?\\d{1,10}"); + static final Pattern LONG_LITERAL_PATTERN = Pattern.compile("[-+]?\\d{1,19}(L|l)"); + static final Pattern DOUBLE_LITERAL_PATTERN = Pattern.compile("[-+]?[0-9]*\\.?[0-9]+(d|D)"); + static final Pattern FLOAT_LITERAL_PATTERN = Pattern.compile("[-+]?[0-9]*\\.?[0-9]+(f|F)"); static Object getLiteral(String value) { if (value == null || value.isEmpty()) { @@ -25,18 +26,37 @@ class LiteralSupport { literal = Boolean.FALSE; } else if (value.equals("null")) { literal = null; - } else if (INTEGER_LITERAL_PATTERN.matcher(value).matches()) { - try { - literal = Integer.parseInt(value); - } catch (NumberFormatException e) { - LOGGER.warn("Unable to parse integer literal: " + value, e); - } - } else if (LONG_LITERAL_PATTERN.matcher(value).matches()) { - try { - literal = Long - .parseLong(value.substring(0, value.length() - 1)); - } catch (NumberFormatException e) { - LOGGER.warn("Unable to parse long literal: " + value, e); + } else { + char firstChar = value.charAt(0); + if (Character.isDigit(firstChar) || firstChar == '-' || firstChar == '+') { + if (INTEGER_LITERAL_PATTERN.matcher(value).matches()) { + try { + literal = Integer.parseInt(value); + } catch (NumberFormatException e) { + LOGGER.warn("Unable to parse integer literal: " + value, e); + } + } else if (LONG_LITERAL_PATTERN.matcher(value).matches()) { + try { + literal = Long + .parseLong(value.substring(0, value.length() - 1)); + } catch (NumberFormatException e) { + LOGGER.warn("Unable to parse long literal: " + value, e); + } + } else if (DOUBLE_LITERAL_PATTERN.matcher(value).matches()) { + try { + literal = Double + .parseDouble(value.substring(0, value.length() - 1)); + } catch (NumberFormatException e) { + LOGGER.warn("Unable to parse double literal: " + value, e); + } + } else if (FLOAT_LITERAL_PATTERN.matcher(value).matches()) { + try { + literal = Float + .parseFloat(value.substring(0, value.length() - 1)); + } catch (NumberFormatException e) { + LOGGER.warn("Unable to parse float literal: " + value, e); + } + } } } return literal; diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Mapper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Mapper.java index 7b6938afd..595b6109a 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Mapper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Mapper.java @@ -1,5 +1,11 @@ package io.quarkus.qute; +/** + * Maps keys to values in a similar way to {@link java.util.Map}. The difference is that it could be stateless, ie. the lookup + * may be performed dynamically. + * + * @see ValueResolvers#mapperResolver() + */ public interface Mapper { Object get(String key); 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 02962bfd7..8f6124cb0 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 @@ -4,6 +4,9 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; +/** + * A result node backed by an array of result nodes. + */ public class MultiResultNode implements ResultNode { private final ResultNode[] results; diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/NamespaceResolver.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/NamespaceResolver.java index 4f0818c60..67d64da06 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/NamespaceResolver.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/NamespaceResolver.java @@ -5,11 +5,20 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Function; -/* - * Namespace resolver. +/** + * Namespace resolvers are used to find the current context object for an expression that starts with a namespace declaration. + *

+ * For example the expression {@code data:colors} declares a namespace {@code data}. + * + * @see EngineBuilder#addNamespaceResolver(NamespaceResolver) */ public interface NamespaceResolver extends Resolver { + /** + * + * @param namespace + * @return a new builder instance + */ static Builder builder(String namespace) { return new Builder(namespace); } @@ -21,10 +30,7 @@ public interface NamespaceResolver extends Resolver { */ String getNamespace(); - /** - * - */ - class Builder { + final class Builder { private final String namespace; private Function> resolve; 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 23eb47d83..406df69c7 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 @@ -45,6 +45,8 @@ class Parser implements Function { static final char START_COMPOSITE_PARAM = '('; static final char END_COMPOSITE_PARAM = ')'; + static final String TYPE_CHECK_NAMESPACE = "[" + Expressions.TYPECHECK_NAMESPACE_PLACEHOLDER + "]."; + private StringBuilder buffer; private State state; private int line; @@ -590,9 +592,7 @@ class Parser implements Function { } if (literal == Result.NOT_FOUND) { if (namespace != null) { - // TODO use constants! - typeCheckInfo = "[" + Expressions.TYPECHECK_NAMESPACE_PLACEHOLDER + "]"; - typeCheckInfo += "." + parts.stream().collect(Collectors.joining(".")); + typeCheckInfo = TYPE_CHECK_NAMESPACE + parts.stream().collect(Collectors.joining(".")); } else if (typeInfos.containsKey(parts.get(0))) { typeCheckInfo = typeInfos.get(parts.get(0)); if (typeCheckInfo != null) { 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 3c2296fc0..55fbc1574 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 @@ -10,20 +10,23 @@ import java.util.concurrent.CompletionStage; public interface ResolutionContext { /** + * Parse and evaluate the expression. * * @param expression - * @return the result of the evaluated expression + * @return the result */ CompletionStage evaluate(String expression); /** + * Evaluate the expression. * * @param expression - * @return the result of the evaluated expression + * @return the result */ CompletionStage evaluate(Expression expression); /** + * Create a child resolution context with the specified data and namespace resolvers. * * @param data * @param namespaceResolversFactories @@ -32,6 +35,7 @@ public interface ResolutionContext { ResolutionContext createChild(Object data, List namespaceResolvers); /** + * Create a child resolution context with the specifiec extending blocks. * * @param extendingBlocks * @return a new child resolution context @@ -59,7 +63,7 @@ public interface ResolutionContext { /** * * @param name - * @return the extending block or null + * @return the extending block for the specified name or null */ SectionBlock getExtendingBlock(String name); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Resolver.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Resolver.java index 225c81b2d..38b98dfe5 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Resolver.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Resolver.java @@ -1,14 +1,21 @@ package io.quarkus.qute; +import io.quarkus.qute.Results.Result; import java.util.concurrent.CompletionStage; +/** + * + * @see ValueResolver + * @see NamespaceResolver + */ public interface Resolver { /** + * This method should return {@link Result#NOT_FOUND} if it's not possible to resolve the context. Any other value is + * considered a valid result, including {@code null}. * * @param context * @return the result - * @see Results#NOT_FOUND */ CompletionStage resolve(EvalContext context); 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 558565073..7eea6bc5d 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 @@ -3,10 +3,16 @@ package io.quarkus.qute; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; -public class Results { +/** + * Result constants. + */ +public final class Results { public static final CompletionStage NOT_FOUND = CompletableFuture.completedFuture(Result.NOT_FOUND); + private Results() { + } + public enum Result { NOT_FOUND, diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelper.java index f123a2381..2f74ccefc 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelper.java @@ -4,6 +4,8 @@ import java.util.concurrent.CompletionStage; /** * Defines the logic of a section node. + * + * @see SectionHelperFactory */ @FunctionalInterface public interface SectionHelper { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java index 61ad00240..f0c2270dc 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java @@ -9,6 +9,8 @@ import java.util.Map; /** * Factory to create a new {@link SectionHelper} based on the {@link SectionInitContextImpl}. + * + * @see EngineBuilder#addSectionHelper(SectionHelperFactory) */ public interface SectionHelperFactory { @@ -16,7 +18,7 @@ public interface SectionHelperFactory { /** * - * @return the list of default aliases + * @return the list of default aliases used to match the helper */ default List getDefaultAliases() { return Collections.emptyList(); @@ -32,8 +34,6 @@ public interface SectionHelperFactory { /** * A nested section tag that matches a name of a block will be added as a block to the current section. - *

- * * * @return the list of block labels */ @@ -41,6 +41,13 @@ public interface SectionHelperFactory { return Collections.emptyList(); } + /** + * By default, all unknown nested sections are ignored, ie. sections with labels not present in the + * {@link #getBlockLabels()}. However, sometimes it might be useful to treat such sections as blocks. See + * {@link IncludeSectionHelper} for an example. + * + * @return true if unknown sections should not be ignored + */ default boolean treatUnknownSectionsAsBlocks() { return false; } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SingleResultNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SingleResultNode.java index 70d0a538e..df5b0d2a3 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SingleResultNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SingleResultNode.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.function.Consumer; /** - * A result node backed by an object value. + * A result node backed by a single object value. */ public class SingleResultNode implements ResultNode { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java index 35b56ef0c..84adbb571 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java @@ -4,11 +4,21 @@ import java.util.Optional; import java.util.Set; /** - * Represents a template definition. + * Represents an immutable template definition. + *

+ * The workflow is as follows: + *

    + *
  1. Create a new template instance via {@link #instance()} or any convenient method
  2. + *
  3. Set the model data
  4. + *
  5. Trigger rendering with {@link TemplateInstance#render()}, {@link TemplateInstance#renderAsync()}, + * {@link TemplateInstance#consume(java.util.function.Consumer)} or subscribe to a publisher returned from + * {@link TemplateInstance#publisher()}
  6. + *
*/ public interface Template { /** + * Template instance represents a rendering configuration. * * @return a new template instance */ diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateData.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateData.java index bb0e8420a..a25cbc5f1 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateData.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateData.java @@ -9,10 +9,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; /** - * A value resolver is automatically generated for a target type. - *

- * Note that non-public members, constructors, static initializers, static, synthetic and void methods are always ignored. - *

+ * This annotation is used to mark a target type for which a value resolver should be automatically generated. Note that + * non-public members, constructors, static initializers, static, synthetic and void methods are always ignored. * * @see ValueResolver */ @@ -27,7 +25,7 @@ public @interface TemplateData { Class target() default TemplateData.class; /** - * The regular expressions that are used to match the members that should be ignored + * The regular expressions that are used to match the members that should be ignored. */ String[] ignore() default {}; diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateLocator.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateLocator.java index ae876f06a..8f593731d 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateLocator.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateLocator.java @@ -4,13 +4,14 @@ import java.io.Reader; import java.util.Optional; /** - * Locates template sources. + * Locates template sources. The locator with higher priority takes precedence. * * @see Engine#getTemplate(String) */ public interface TemplateLocator extends WithPriority { /** + * Must return {@link Optional#empty()} if it's not possible to locate a template with the specified id. * * @param id * @return the template location for the given id 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 707ad5097..d3b01b4e1 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 @@ -1,7 +1,14 @@ package io.quarkus.qute; +import io.quarkus.qute.Results.Result; + /** - * Value resolver. + * Value resolvers are used when evaluating expressions. + *

+ * First the resolvers that apply to the given {@link EvalContext} are filtered. Then the resolver with highest priority is used + * to resolve the data. If {@link Result#NOT_FOUND} is returned the next available resolver is tried. + * + * @see EvalContext */ public interface ValueResolver extends Resolver, WithPriority { diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/EngineBuilderTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EngineBuilderTest.java new file mode 100644 index 000000000..555b979f0 --- /dev/null +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EngineBuilderTest.java @@ -0,0 +1,22 @@ +package io.quarkus.qute; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +public class EngineBuilderTest { + + @Test + public void testDuplicateNamespace() { + try { + Engine.builder().addNamespaceResolver(NamespaceResolver.builder("foo").resolve(e -> { + return null; + }).build()).addNamespaceResolver(NamespaceResolver.builder("foo").resolve(e -> { + return null; + }).build()); + fail(); + } catch (IllegalArgumentException expected) { + } + } + +} diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/LiteralSupportTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/LiteralSupportTest.java new file mode 100644 index 000000000..eee1d5691 --- /dev/null +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/LiteralSupportTest.java @@ -0,0 +1,40 @@ +package io.quarkus.qute; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +public class LiteralSupportTest { + + @Test + public void testNumbers() { + assertEquals(Integer.valueOf(43), LiteralSupport.getLiteral("43")); + assertEquals(Long.valueOf(1000), LiteralSupport.getLiteral("1000l")); + assertEquals(Long.valueOf(-10), LiteralSupport.getLiteral("-10L")); + assertEquals(Double.valueOf(1.0d), LiteralSupport.getLiteral("1d")); + assertEquals(Double.valueOf(2.12d), LiteralSupport.getLiteral("+2.12d")); + assertEquals(Double.valueOf(-2.12d), LiteralSupport.getLiteral("-2.12d")); + assertEquals(Double.valueOf(123.4d), LiteralSupport.getLiteral("123.4D")); + assertEquals(Float.valueOf(2.12f), LiteralSupport.getLiteral("2.12f")); + assertEquals(Float.valueOf(2.12f), LiteralSupport.getLiteral("2.12F")); + } + + @Test + public void testBooleans() { + assertEquals(Boolean.TRUE, LiteralSupport.getLiteral("true")); + assertEquals(Boolean.FALSE, LiteralSupport.getLiteral("false")); + } + + @Test + public void testNull() { + assertNull(LiteralSupport.getLiteral("null")); + } + + @Test + public void testStrings() { + assertEquals("foo", LiteralSupport.getLiteral("'foo'")); + assertEquals("foo", LiteralSupport.getLiteral("\"foo\"")); + } + +}