From 38ea454b8b9ed00830c097e7e72b7aece268a0a9 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 8 Nov 2024 08:43:36 +0100 Subject: [PATCH] Qute: fix generation of qute-i18n-examples - handle localized enums correctly - fixes #44366 --- .../MessageBundleMethodBuildItem.java | 17 ++++- .../deployment/MessageBundleProcessor.java | 25 ++++++-- .../MessageBundleEnumExampleFileTest.java | 64 +++++++++++++++++++ .../java/io/quarkus/qute/i18n/Message.java | 3 +- 4 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumExampleFileTest.java diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleMethodBuildItem.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleMethodBuildItem.java index 56809719f7b0a..725999bbd0b9a 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleMethodBuildItem.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleMethodBuildItem.java @@ -18,15 +18,17 @@ public final class MessageBundleMethodBuildItem extends MultiBuildItem { private final MethodInfo method; private final String template; private final boolean isDefaultBundle; + private final boolean hasGeneratedTemplate; MessageBundleMethodBuildItem(String bundleName, String key, String templateId, MethodInfo method, String template, - boolean isDefaultBundle) { + boolean isDefaultBundle, boolean hasGeneratedTemplate) { this.bundleName = bundleName; this.key = key; this.templateId = templateId; this.method = method; this.template = template; this.isDefaultBundle = isDefaultBundle; + this.hasGeneratedTemplate = hasGeneratedTemplate; } public String getBundleName() { @@ -54,6 +56,11 @@ public MethodInfo getMethod() { return method; } + /** + * + * @return {@code true} if there is a corresponding method declared on the message bundle interface + * @see #getMethod() + */ public boolean hasMethod() { return method != null; } @@ -79,6 +86,14 @@ public boolean isDefaultBundle() { return isDefaultBundle; } + /** + * + * @return {@code true} if the template was generated, e.g. a message bundle method for an enum + */ + public boolean hasGeneratedTemplate() { + return hasGeneratedTemplate; + } + /** * * @return the path diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java index f67dd11dbc181..5842bbd715abc 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java @@ -702,8 +702,22 @@ void generateExamplePropertiesFiles(List messageBu List messages = entry.getValue(); messages.sort(Comparator.comparing(MessageBundleMethodBuildItem::getKey)); Path exampleProperties = generatedExamplesDir.resolve(entry.getKey() + ".properties"); - Files.write(exampleProperties, - messages.stream().map(m -> m.getMethod().name() + "=" + m.getTemplate()).collect(Collectors.toList())); + List lines = new ArrayList<>(); + for (MessageBundleMethodBuildItem m : messages) { + if (m.hasMethod()) { + if (m.hasGeneratedTemplate()) { + // Skip messages with generated templates + continue; + } + // Keys are mapped to method names + lines.add(m.getMethod().name() + "=" + m.getTemplate()); + } else { + // No corresponding method declared - use the key instead + // For example, there is no method for generated enum constant message keys + lines.add(m.getKey() + "=" + m.getTemplate()); + } + } + Files.write(exampleProperties, lines); } } @@ -992,6 +1006,7 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d } keyMap.put(key, new SimpleMessageMethod(method)); + boolean generatedTemplate = false; String messageTemplate = messageTemplates.get(method.name()); if (messageTemplate == null) { messageTemplate = getMessageAnnotationValue(messageAnnotation); @@ -1043,6 +1058,7 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d } generatedMessageTemplate.append("{/when}"); messageTemplate = generatedMessageTemplate.toString(); + generatedTemplate = true; } } } @@ -1068,7 +1084,7 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d } MessageBundleMethodBuildItem messageBundleMethod = new MessageBundleMethodBuildItem(bundleName, key, templateId, - method, messageTemplate, defaultBundleInterface == null); + method, messageTemplate, defaultBundleInterface == null, generatedTemplate); messageTemplateMethods .produce(messageBundleMethod); @@ -1139,8 +1155,7 @@ private void generateEnumConstantMessageMethod(ClassCreator bundleCreator, Strin } MessageBundleMethodBuildItem messageBundleMethod = new MessageBundleMethodBuildItem(bundleName, enumConstantKey, - templateId, null, messageTemplate, - defaultBundleInterface == null); + templateId, null, messageTemplate, defaultBundleInterface == null, true); messageTemplateMethods.produce(messageBundleMethod); MethodCreator enumConstantMethod = bundleCreator.getMethodCreator(enumConstantKey, diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumExampleFileTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumExampleFileTest.java new file mode 100644 index 0000000000000..008a289fa6340 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumExampleFileTest.java @@ -0,0 +1,64 @@ +package io.quarkus.qute.deployment.i18n; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Properties; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.i18n.Message; +import io.quarkus.qute.i18n.MessageBundle; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class MessageBundleEnumExampleFileTest { + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot(root -> root + .addClasses(Messages.class, MyEnum.class) + .addAsResource(new StringAsset(""" + myEnum_ON=On + myEnum_OFF=Off + myEnum_UNDEFINED=Undefined + """), + "messages/enu.properties")); + + @ProdBuildResults + ProdModeTestResults testResults; + + @Test + public void testExampleProperties() throws FileNotFoundException, IOException { + Path path = testResults.getBuildDir().resolve("qute-i18n-examples").resolve("enu.properties"); + assertTrue(path.toFile().canRead()); + Properties props = new Properties(); + props.load(new FileInputStream(path.toFile())); + assertEquals(3, props.size()); + assertTrue(props.containsKey("myEnum_ON")); + assertTrue(props.containsKey("myEnum_OFF")); + assertTrue(props.containsKey("myEnum_UNDEFINED")); + } + + @MessageBundle(value = "enu", locale = "en") + public interface Messages { + + // Replaced with: + // @Message("{#when myEnum}" + // + "{#is ON}{enu:myEnum_ON}" + // + "{#is OFF}{enu:myEnum_OFF}" + // + "{#is UNDEFINED}{enu:myEnum_UNDEFINED}" + // + "{/when}") + @Message + String myEnum(MyEnum myEnum); + + } + +} diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java index 93c5fbe6b1327..b8b8a43ae5955 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java @@ -27,8 +27,7 @@ * There is a convenient way to localize enums. *

* If there is a message bundle method that accepts a single parameter of an enum type and has no message template defined then - * it - * receives a generated template: + * it receives a generated template: * *

  * {#when enumParamName}