Skip to content

Commit

Permalink
Qute user tags - use defaulted keys if appropriate
Browse files Browse the repository at this point in the history
- i.e. for params that define no key and contain only single part
- also use Mapper instead of Map for the data passed to a template
- Mapper value resolver has priority 15, i.e. is called before a
generated value resolver
- resolves #21855
- related to #21861
  • Loading branch information
mkouba committed Dec 10, 2021
1 parent 8bc2149 commit 4b033a5
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public ParametersInfo getParameters() {
return ParametersInfo.builder()
.addParameter(ALIAS, EMPTY)
.addParameter(IN, EMPTY)
.addParameter(new Parameter(ITERABLE, null, true))
.addParameter(Parameter.builder(ITERABLE).optional())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package io.quarkus.qute;

import io.quarkus.qute.SectionHelperFactory.ParametersInfo;
import java.util.Objects;
import java.util.function.Predicate;

/**
* Definition of a section parameter.
* Definition of a section factory parameter.
*
* @see ParametersInfo
* @see SectionHelperFactory#getParameters()
*/
public class Parameter {
public final class Parameter {

public static Builder builder(String name) {
return new Builder(name);
}

public static final String EMPTY = "$empty$";

Expand All @@ -18,10 +24,19 @@ public class Parameter {

public final boolean optional;

public final Predicate<String> valuePredicate;

private static final Predicate<String> ALWAYS_TRUE = v -> true;

public Parameter(String name, String defaultValue, boolean optional) {
this.name = name;
this(name, defaultValue, optional, ALWAYS_TRUE);
}

private Parameter(String name, String defaultValue, boolean optional, Predicate<String> valuePredicate) {
this.name = Objects.requireNonNull(name);
this.defaultValue = defaultValue;
this.optional = optional;
this.valuePredicate = valuePredicate != null ? valuePredicate : ALWAYS_TRUE;
}

public String getName() {
Expand All @@ -32,14 +47,24 @@ public String getDefaultValue() {
return defaultValue;
}

public boolean hasDefatulValue() {
public boolean hasDefaultValue() {
return defaultValue != null;
}

public boolean isOptional() {
return optional;
}

/**
* Allows a factory parameter to refuse a value of an unnamed actual parameter.
*
* @param value
* @return {@code true} if the value is acceptable, {@code false} otherwise
*/
public boolean accepts(String value) {
return valuePredicate.test(value);
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
Expand All @@ -48,4 +73,37 @@ public String toString() {
return builder.toString();
}

public static class Builder {

private final String name;
private String defaultValue;
private boolean optional;
private Predicate<String> valuePredicate;

public Builder(String name) {
this.name = name;
this.optional = false;
}

public Builder defaultValue(String defaultValue) {
this.defaultValue = defaultValue;
return this;
}

public Builder optional() {
this.optional = true;
return this;
}

public Builder valuePredicate(Predicate<String> valuePredicate) {
this.valuePredicate = valuePredicate;
return this;
}

public Parameter build() {
return new Parameter(name, defaultValue, optional, valuePredicate);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
Expand Down Expand Up @@ -548,52 +549,34 @@ private void processParams(String tag, String label, Iterator<String> iter, Sect
}
}

Predicate<String> included = params::containsKey;
// Then process positional params
if (actualSize < factoryParams.size()) {
// The number of actual params is less than factory params
// We need to choose the best fit for positional params
for (String param : paramValues) {
Parameter found = null;
for (Parameter factoryParam : factoryParams) {
// Prefer params with no default value
if (factoryParam.defaultValue == null && !params.containsKey(factoryParam.name)) {
found = factoryParam;
params.put(factoryParam.name, param);
break;
}
}
if (found == null) {
for (Parameter factoryParam : factoryParams) {
if (!params.containsKey(factoryParam.name)) {
found = factoryParam;
params.put(factoryParam.name, param);
break;
}
}
Parameter found = findFactoryParameter(param, factoryParams, included, true);
if (found != null) {
params.put(found.name, param);
}
}
} else {
// The number of actual params is greater or equals to factory params
int generatedIdx = 0;
for (String param : paramValues) {
// Positional param
Parameter found = null;
for (Parameter factoryParam : factoryParams) {
if (!params.containsKey(factoryParam.name)) {
found = factoryParam;
params.put(factoryParam.name, param);
break;
}
}
if (found == null) {
Parameter found = findFactoryParameter(param, factoryParams, included, false);
if (found != null) {
params.put(found.name, param);
} else {
params.put("" + generatedIdx++, param);
}
}
}

// Use the default values if needed
factoryParams.stream()
.filter(Parameter::hasDefatulValue)
.filter(Parameter::hasDefaultValue)
.forEach(p -> params.putIfAbsent(p.name, p.defaultValue));

// Find undeclared mandatory params
Expand All @@ -609,6 +592,24 @@ private void processParams(String tag, String label, Iterator<String> iter, Sect
params.forEach(block::addParameter);
}

private Parameter findFactoryParameter(String paramValue, List<Parameter> factoryParams, Predicate<String> included,
boolean noDefaultValueTakesPrecedence) {
if (noDefaultValueTakesPrecedence) {
for (Parameter param : factoryParams) {
// Params with no default value take precedence
if (param.accepts(paramValue) && !param.hasDefaultValue() && !included.test(param.name)) {
return param;
}
}
}
for (Parameter param : factoryParams) {
if (param.accepts(paramValue) && !included.test(param.name)) {
return param;
}
}
return null;
}

/**
*
* @param part
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@

import java.util.Map;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;

class ResolutionContextImpl implements ResolutionContext {

private final Object data;
private final Evaluator evaluator;
private final Map<String, SectionBlock> extendingBlocks;
private final TemplateInstance templateInstance;
private final Function<String, Object> attributeFun;

ResolutionContextImpl(Object data,
Evaluator evaluator, Map<String, SectionBlock> extendingBlocks, TemplateInstance templateInstance) {
Evaluator evaluator, Map<String, SectionBlock> extendingBlocks, Function<String, Object> attributeFun) {
this.data = data;
this.evaluator = evaluator;
this.extendingBlocks = extendingBlocks;
this.templateInstance = templateInstance;
this.attributeFun = attributeFun;
}

@Override
Expand Down Expand Up @@ -53,7 +54,7 @@ public SectionBlock getExtendingBlock(String name) {

@Override
public Object getAttribute(String key) {
return templateInstance.getAttribute(key);
return attributeFun.apply(key);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public String asMessage() {
if (name != null) {
Object base = getBase().orElse(null);
List<Expression> params = getParams();
boolean isDataMap = (base instanceof Map) && ((Map<?, ?>) base).containsKey(TemplateInstanceBase.DATA_MAP_KEY);
boolean isDataMap = isDataMap(base);
// Entry "foo" not found in the data map
// Property "foo" not found on base object "org.acme.Bar"
// Method "getDiscount(value)" not found on base object "org.acme.Item"
Expand Down Expand Up @@ -190,6 +190,15 @@ public String asMessage() {
}
}

private boolean isDataMap(Object base) {
if (base instanceof Map) {
return ((Map<?, ?>) base).containsKey(TemplateInstanceBase.DATA_MAP_KEY);
} else if (base instanceof Mapper) {
return ((Mapper) base).get(TemplateInstanceBase.DATA_MAP_KEY) != null;
}
return false;
}

@Override
public String toString() {
return "NOT_FOUND";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.qute;

import java.util.Map;
import java.util.concurrent.CompletionStage;

/**
Expand Down Expand Up @@ -28,6 +29,14 @@ public interface SectionResolutionContext {
*/
ResolutionContext resolutionContext();

/**
*
* @param data
* @param extendingBlocks
* @return a new resolution context
*/
ResolutionContext newResolutionContext(Object data, Map<String, SectionBlock> extendingBlocks);

/**
* Execute the main block with the current resolution context.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,34 @@ default List<String> getDefaultAliases() {
}

/**
* A factory may define {@code factory parameters} for the start tag of any section block. A factory {@link Parameter} has a
* name and optional default value. The default value is automatically assigned if no other value is set by a parser. A
* parameter may be optional. A non-optional parameter that has no value assigned results in a parser error.
* <p>
* A section block in a template defines the {@code actual parameters}:
*
* @return the info about the expected parameters
* <pre>
* {! The value is "item.isActive". The name is not defined. !}
* {#if item.isActive}{/}
*
* {! The name is "age" and the value is "10". !}
* {#let age=10}{/}
* </pre>
*
* The actual parameters are parsed taking the factory parameters into account:
* <ol>
* <li>Named actual params are processed first and the relevant values are assigned, e.g. the param with name {@code age}
* has the
* value {@code 10},</li>
* <li>Then, if the number of actual params is greater or equals to the number of factory params the values are set
* according to position of factory params,</li>
* <li>Otherwise, the values are set according to position but params with no default value take precedence.</li>
* <li>Finally, all unset parameters that define a default value are initialized with the default value.</li>
* </ol>
*
* @return the factory parameters
* @see #cacheFactoryConfig()
* @see BlockInfo#getParameters()
*/
default ParametersInfo getParameters() {
return ParametersInfo.EMPTY;
Expand Down Expand Up @@ -93,6 +118,11 @@ interface BlockInfo extends ParserDelegate {

String getLabel();

/**
* Undeclared params with default values are included.
*
* @return the map of parameters
*/
Map<String, String> getParameters();

default String getParameter(String name) {
Expand Down Expand Up @@ -196,6 +226,10 @@ default SectionBlock getBlock(String label) {

}

/**
*
* @see Parameter
*/
public static final class ParametersInfo implements Iterable<List<Parameter>> {

public static Builder builder() {
Expand Down Expand Up @@ -236,19 +270,27 @@ public static class Builder {
}

public Builder addParameter(String name) {
return addParameter(SectionHelperFactory.MAIN_BLOCK_NAME, name, null);
return addParameter(Parameter.builder(name));
}

public Builder addParameter(String name, String defaultValue) {
return addParameter(SectionHelperFactory.MAIN_BLOCK_NAME, name, defaultValue);
return addParameter(Parameter.builder(name).defaultValue(defaultValue));
}

public Builder addParameter(Parameter.Builder param) {
return addParameter(param.build());
}

public Builder addParameter(Parameter param) {
return addParameter(SectionHelperFactory.MAIN_BLOCK_NAME, param);
}

public Builder addParameter(String blockLabel, String name, String defaultValue) {
return addParameter(blockLabel, new Parameter(name, defaultValue, false));
return addParameter(blockLabel, Parameter.builder(name).defaultValue(defaultValue));
}

public Builder addParameter(String blockLabel, Parameter.Builder parameter) {
return addParameter(blockLabel, parameter.build());
}

public Builder addParameter(String blockLabel, Parameter parameter) {
Expand Down
Loading

0 comments on commit 4b033a5

Please sign in to comment.