Skip to content

Commit

Permalink
Type-safe message bundles - support dynamic keys..
Browse files Browse the repository at this point in the history
- ...in templates
- resolves #11567
  • Loading branch information
mkouba committed Aug 25, 2020
1 parent 20357ea commit 841a0ef
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.qute.deployment;

import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.i18n.MessageBundles;

final class Descriptors {

static final MethodDescriptor TEMPLATE_INSTANCE = MethodDescriptor.ofMethod(Template.class, "instance",
TemplateInstance.class);
static final MethodDescriptor TEMPLATE_INSTANCE_DATA = MethodDescriptor.ofMethod(TemplateInstance.class, "data",
TemplateInstance.class, String.class, Object.class);
static final MethodDescriptor TEMPLATE_INSTANCE_RENDER = MethodDescriptor.ofMethod(TemplateInstance.class, "render",
String.class);
static final MethodDescriptor BUNDLES_GET_TEMPLATE = MethodDescriptor.ofMethod(MessageBundles.class, "getTemplate",
Template.class, String.class);

}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.quarkus.qute.deployment;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Stream;

import org.jboss.jandex.DotName;

import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.api.CheckedTemplate;
import io.quarkus.qute.api.ResourcePath;
import io.quarkus.qute.i18n.Localized;
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;
import io.quarkus.qute.i18n.MessageParam;

final class Names {

static final DotName BUNDLE = DotName.createSimple(MessageBundle.class.getName());
static final DotName MESSAGE = DotName.createSimple(Message.class.getName());
static final DotName MESSAGE_PARAM = DotName.createSimple(MessageParam.class.getName());
static final DotName LOCALIZED = DotName.createSimple(Localized.class.getName());
static final DotName RESOURCE_PATH = DotName.createSimple(ResourcePath.class.getName());
static final DotName TEMPLATE = DotName.createSimple(Template.class.getName());
static final DotName ITERABLE = DotName.createSimple(Iterable.class.getName());
static final DotName ITERATOR = DotName.createSimple(Iterator.class.getName());
static final DotName STREAM = DotName.createSimple(Stream.class.getName());
static final DotName MAP = DotName.createSimple(Map.class.getName());
static final DotName MAP_ENTRY = DotName.createSimple(Entry.class.getName());
static final DotName COLLECTION = DotName.createSimple(Collection.class.getName());
static final DotName CHECKED_TEMPLATE = DotName.createSimple(CheckedTemplate.class.getName());
static final DotName TEMPLATE_INSTANCE = DotName.createSimple(TemplateInstance.class.getName());

private Names() {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
Expand Down Expand Up @@ -92,7 +91,6 @@
import io.quarkus.qute.TemplateLocator;
import io.quarkus.qute.UserTagSectionHelper;
import io.quarkus.qute.Variant;
import io.quarkus.qute.api.CheckedTemplate;
import io.quarkus.qute.api.ResourcePath;
import io.quarkus.qute.deployment.TemplatesAnalysisBuildItem.TemplateAnalysis;
import io.quarkus.qute.deployment.TypeCheckExcludeBuildItem.Check;
Expand All @@ -114,22 +112,9 @@

public class QuteProcessor {

private static final Logger LOGGER = Logger.getLogger(QuteProcessor.class);

public static final DotName RESOURCE_PATH = DotName.createSimple(ResourcePath.class.getName());
public static final DotName TEMPLATE = DotName.createSimple(Template.class.getName());

static final DotName ITERABLE = DotName.createSimple(Iterable.class.getName());
static final DotName ITERATOR = DotName.createSimple(Iterator.class.getName());
static final DotName STREAM = DotName.createSimple(Stream.class.getName());
static final DotName MAP = DotName.createSimple(Map.class.getName());
public static final DotName RESOURCE_PATH = Names.RESOURCE_PATH;

static final DotName MAP_ENTRY = DotName.createSimple(Entry.class.getName());
static final DotName COLLECTION = DotName.createSimple(Collection.class.getName());
static final DotName STRING = DotName.createSimple(String.class.getName());

static final DotName DOTNAME_CHECKED_TEMPLATE = DotName.createSimple(CheckedTemplate.class.getName());
static final DotName DOTNAME_TEMPLATE_INSTANCE = DotName.createSimple(TemplateInstance.class.getName());
private static final Logger LOGGER = Logger.getLogger(QuteProcessor.class);

@BuildStep
FeatureBuildItem feature() {
Expand Down Expand Up @@ -195,9 +180,9 @@ List<CheckedTemplateBuildItem> collectTemplateTypeInfo(BeanArchiveIndexBuildItem
}
String supportedAdaptors;
if (adaptors.isEmpty()) {
supportedAdaptors = DOTNAME_TEMPLATE_INSTANCE + " is supported";
supportedAdaptors = Names.TEMPLATE_INSTANCE + " is supported";
} else {
StringBuffer strbuf = new StringBuffer(DOTNAME_TEMPLATE_INSTANCE.toString());
StringBuffer strbuf = new StringBuffer(Names.TEMPLATE_INSTANCE.toString());
List<String> adaptorsList = new ArrayList<>(adaptors.size());
for (DotName name : adaptors.keySet()) {
adaptorsList.add(name.toString());
Expand All @@ -209,7 +194,7 @@ List<CheckedTemplateBuildItem> collectTemplateTypeInfo(BeanArchiveIndexBuildItem
supportedAdaptors = strbuf.append(" are supported").toString();
}

for (AnnotationInstance annotation : index.getIndex().getAnnotations(DOTNAME_CHECKED_TEMPLATE)) {
for (AnnotationInstance annotation : index.getIndex().getAnnotations(Names.CHECKED_TEMPLATE)) {
if (annotation.target().kind() != Kind.CLASS)
continue;
ClassInfo classInfo = annotation.target().asClass();
Expand All @@ -228,7 +213,7 @@ List<CheckedTemplateBuildItem> collectTemplateTypeInfo(BeanArchiveIndexBuildItem
DotName returnTypeName = methodInfo.returnType().asClassType().name();
CheckedTemplateAdapter adaptor = null;
// if it's not the default template instance, try to find an adapter
if (!returnTypeName.equals(DOTNAME_TEMPLATE_INSTANCE)) {
if (!returnTypeName.equals(Names.TEMPLATE_INSTANCE)) {
adaptor = adaptors.get(returnTypeName);
if (adaptor == null)
throw new TemplateException("Incompatible checked template return type: " + methodInfo.returnType()
Expand Down Expand Up @@ -881,9 +866,9 @@ void validateTemplateInjectionPoints(QuteConfig config, List<TemplatePathBuildIt

for (InjectionPointInfo injectionPoint : validationPhase.getContext().get(BuildExtension.Key.INJECTION_POINTS)) {

if (injectionPoint.getRequiredType().name().equals(TEMPLATE)) {
if (injectionPoint.getRequiredType().name().equals(Names.TEMPLATE)) {

AnnotationInstance resourcePath = injectionPoint.getRequiredQualifier(RESOURCE_PATH);
AnnotationInstance resourcePath = injectionPoint.getRequiredQualifier(Names.RESOURCE_PATH);
String name;
if (resourcePath != null) {
name = resourcePath.value().asString();
Expand Down Expand Up @@ -942,7 +927,7 @@ public boolean test(Check check) {
return true;
}
// Collection.contains()
if (check.numberOfParameters == 1 && check.classNameEquals(COLLECTION) && check.name.equals("contains")) {
if (check.numberOfParameters == 1 && check.classNameEquals(Names.COLLECTION) && check.name.equals("contains")) {
return true;
}
return false;
Expand Down Expand Up @@ -1036,23 +1021,23 @@ static void processLoopHint(Match match, IndexView index, Expression expression)
match.getParameterizedTypeArguments(), match.getTypeParameters(), new HashMap<>(), index), index);
Function<Type, Type> firstParamType = t -> t.asParameterizedType().arguments().get(0);
// Iterable<Item> => Item
matchType = extractMatchType(closure, ITERABLE, firstParamType);
matchType = extractMatchType(closure, Names.ITERABLE, firstParamType);
if (matchType == null) {
// Stream<Long> => Long
matchType = extractMatchType(closure, STREAM, firstParamType);
matchType = extractMatchType(closure, Names.STREAM, firstParamType);
}
if (matchType == null) {
// Entry<K,V> => Entry<String,Item>
matchType = extractMatchType(closure, MAP, t -> {
matchType = extractMatchType(closure, Names.MAP, t -> {
Type[] args = new Type[2];
args[0] = t.asParameterizedType().arguments().get(0);
args[1] = t.asParameterizedType().arguments().get(1);
return ParameterizedType.create(MAP_ENTRY, args, null);
return ParameterizedType.create(Names.MAP_ENTRY, args, null);
});
}
if (matchType == null) {
// Iterator<Item> => Item
matchType = extractMatchType(closure, ITERATOR, firstParamType);
matchType = extractMatchType(closure, Names.ITERATOR, firstParamType);
}
}
if (matchType != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public interface AppMessages {
@Message("Hello {name}!")
String hello_name(String name);

@Message("Hello {name} {surname}!")
String hello_fullname(String name, String surname);

// key=hello_with_if_section
@Message(key = UNDERSCORED_ELEMENT_NAME, value = "{#if count eq 1}"
+ "{msg:hello_name('you guy')}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ public class MessageBundleTest {
"templates/foo.html")
.addAsResource(new StringAsset(
"hello=Hallo Welt!\nhello_name=Hallo {name}!"),
"messages/msg_de.properties"));
"messages/msg_de.properties")
.addAsResource(new StringAsset(
"{msg:message('hello')} {msg:message(key,'Malachi',surname)}"),
"templates/dynamic.html"));

@Inject
AppMessages messages;
Expand Down Expand Up @@ -71,6 +74,8 @@ public void testResolvers() {
assertEquals("Hallo Welt! Hallo Jachym! Hello you guys! Hello alpha! Hello! Hello foo from alpha!",
foo.instance().setAttribute(MessageBundles.ATTRIBUTE_LOCALE, Locale.GERMAN).render());
assertEquals("Dot test!", engine.parse("{msg:['dot.test']}").render());
assertEquals("Hello world! Hello Malachi Constant!",
engine.getTemplate("dynamic").data("key", "hello_fullname").data("surname", "Constant").render());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public static <T> T get(Class<T> bundleInterface, Localized localized) {
if (!bundleInterface.isAnnotationPresent(MessageBundle.class)
&& !bundleInterface.isAnnotationPresent(Localized.class)) {
throw new IllegalArgumentException(
"Message bundle interface must be annotated either with @Bundle or with @Localized: " + bundleInterface);
"Message bundle interface must be annotated either with @MessageBundle or with @Localized: "
+ bundleInterface);
}
InstanceHandle<T> handle = localized != null ? Arc.container().instance(bundleInterface, localized)
: Arc.container().instance(bundleInterface);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,24 @@
@SuppressWarnings("rawtypes")
public final class EvaluatedParams {

static final EvaluatedParams EMPTY;

static {
CompletableFuture<Void> empty = new CompletableFuture<Void>();
empty.complete(null);
EMPTY = new EvaluatedParams(empty, new CompletableFuture<?>[0]);
}

/**
*
* @param context
* @return the evaluated params
*/
public static EvaluatedParams evaluate(EvalContext context) {
List<Expression> params = context.getParams();
if (params.size() == 1) {
if (params.isEmpty()) {
return EMPTY;
} else if (params.size() == 1) {
return new EvaluatedParams(context.evaluate(params.get(0)));
}
CompletableFuture<?>[] results = new CompletableFuture<?>[params.size()];
Expand All @@ -29,6 +39,28 @@ public static EvaluatedParams evaluate(EvalContext context) {
return new EvaluatedParams(CompletableFuture.allOf(results), results);
}

public static EvaluatedParams evaluateMessageKey(EvalContext context) {
List<Expression> params = context.getParams();
if (params.isEmpty()) {
throw new IllegalArgumentException("No params to evaluate");
}
return new EvaluatedParams(context.evaluate(params.get(0)));
}

public static EvaluatedParams evaluateMessageParams(EvalContext context) {
List<Expression> params = context.getParams();
if (params.size() < 2) {
return EMPTY;
}
CompletableFuture<?>[] results = new CompletableFuture<?>[params.size() - 1];
int i = 0;
Iterator<Expression> it = params.subList(1, params.size()).iterator();
while (it.hasNext()) {
results[i++] = context.evaluate(it.next()).toCompletableFuture();
}
return new EvaluatedParams(CompletableFuture.allOf(results), results);
}

public final CompletionStage stage;
private final CompletableFuture<?>[] results;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class ExpressionTest {
@Test
public void testExpressions() throws InterruptedException, ExecutionException {
verify("data:name.value", "data", null, name("name", "name"), name("value", "value"));
verify("data:getName('value')", "data", null, virtualMethod("getName", ExpressionImpl.from("'value'")));
// ignore adjacent separators
verify("name..value", null, null, name("name"), name("value"));
verify("0", null, CompletableFuture.completedFuture(Integer.valueOf(0)), name("0", "|java.lang.Integer|"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ public void tesNamespaceResolver() {

@Override
public CompletionStage<Object> resolve(EvalContext context) {
return context.getName().equals("foo") ? CompletableFuture.completedFuture("bar") : Results.NOT_FOUND;
if (!context.getName().equals("foo")) {
return Results.NOT_FOUND;
}
CompletableFuture<Object> ret = new CompletableFuture<>();
context.evaluate(context.getParams().get(0)).whenComplete((r, e) -> {
ret.complete(r);
});
return ret;
}

@Override
Expand All @@ -25,7 +32,7 @@ public String getNamespace() {
})
.build();

assertEquals("bar", engine.parse("{global:foo}").render(null));
assertEquals("bar", engine.parse("{global:foo('bar')}").render(null));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ private Descriptors() {
"evaluate",
EvaluatedParams.class,
EvalContext.class);
public static final MethodDescriptor EVALUATED_PARAMS_EVALUATE_MESSAGE_KEY = MethodDescriptor.ofMethod(
EvaluatedParams.class,
"evaluateMessageKey",
EvaluatedParams.class,
EvalContext.class);
public static final MethodDescriptor EVALUATED_PARAMS_EVALUATE_MESSAGE_PARAMS = MethodDescriptor.ofMethod(
EvaluatedParams.class,
"evaluateMessageParams",
EvaluatedParams.class,
EvalContext.class);
public static final MethodDescriptor EVALUATED_PARAMS_GET_RESULT = MethodDescriptor.ofMethod(EvaluatedParams.class,
"getResult",
Object.class,
Expand Down

0 comments on commit 841a0ef

Please sign in to comment.