You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by di...@apache.org on 2021/07/23 10:32:03 UTC

[sling-org-apache-sling-scripting-sightly] 01/02: add icu MessageFormat as option for complex patterns

This is an automated email from the ASF dual-hosted git repository.

diru pushed a commit to branch issue/SLING-10654
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly.git

commit 474697c67aa7571d97f3ae7e8720c2daa7bd7150
Author: Dirk Rudolph <dr...@adobe.com>
AuthorDate: Mon Jul 19 09:37:10 2021 +0200

    add icu MessageFormat as option for complex patterns
    
    this adds an optional dependnecy to the icu4j classes. If they do not exist in the runtime
    the logic falls back to the existing behaviour.
---
 bnd.bnd                                            |  2 +
 pom.xml                                            | 28 ++++++++
 .../engine/extension/FormatFilterExtension.java    | 52 ++++++++++++--
 .../extension/FormatFilterExtensionTest.java       | 79 ++++++++++++++++++++++
 4 files changed, 157 insertions(+), 4 deletions(-)

diff --git a/bnd.bnd b/bnd.bnd
index 768c55b..c4c672d 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -12,4 +12,6 @@ Import-Package:     org.apache.sling.scripting.sightly.compiler.*;resolution:=op
                     org.apache.sling.commons.compiler.*;resolution:=optional, \\
                     org.apache.sling.commons.classloader.*;resolution:=optional, \\
                     org.apache.sling.models.*;resolution:=optional, \\
+                    org.apache.sling.models.*;resolution:=optional, \\
+                    com.ibm.icu.*;resolution:=optional, \\
                     *
diff --git a/pom.xml b/pom.xml
index c9a63a1..a30915f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,15 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>com.github.spotbugs</groupId>
                 <artifactId>spotbugs-maven-plugin</artifactId>
                 <version>3.1.11</version>
@@ -253,6 +262,13 @@
             <scope>provided</scope>
         </dependency>
 
+        <!-- icu -->
+        <dependency>
+            <groupId>com.ibm.icu</groupId>
+            <artifactId>icu4j</artifactId>
+            <version>69.1</version>
+        </dependency>
+
         <dependency>
             <groupId>org.jetbrains</groupId>
             <artifactId>annotations</artifactId>
@@ -290,6 +306,18 @@
             <version>2.5.0</version>
             <scope>test</scope>
         </dependency>
+
+        <!-- benchmark -->
+        <dependency>
+            <groupId>org.openjdk.jmh</groupId>
+            <artifactId>jmh-core</artifactId>
+            <version>1.32</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openjdk.jmh</groupId>
+            <artifactId>jmh-generator-annprocess</artifactId>
+            <version>1.32</version>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java
index 06527cf..f7cf405 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java
@@ -38,6 +38,10 @@ import org.apache.sling.scripting.sightly.extension.RuntimeExtension;
 import org.apache.sling.scripting.sightly.render.RenderContext;
 import org.apache.sling.scripting.sightly.render.RuntimeObjectModel;
 import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ibm.icu.text.MessageFormat;
 
 @Component(
         service = RuntimeExtension.class,
@@ -47,7 +51,11 @@ import org.osgi.service.component.annotations.Component;
 )
 public class FormatFilterExtension implements RuntimeExtension {
 
+    static boolean hasIcuSupport;
+
+    private static final Logger LOG = LoggerFactory.getLogger(FormatFilterExtension.class);
     private static final Pattern PLACEHOLDER_REGEX = Pattern.compile("\\{\\d+}");
+    private static final Pattern COMPLEX_PLACEHOLDER_REGEX = Pattern.compile("\\{\\d+,[^}]+}");
     private static final String FORMAT_OPTION = "format";
     private static final String TYPE_OPTION = "type";
     private static final String LOCALE_OPTION = "locale";
@@ -57,6 +65,16 @@ public class FormatFilterExtension implements RuntimeExtension {
     private static final String NUMBER_FORMAT_TYPE = "number";
     private static final String STRING_FORMAT_TYPE = "string";
 
+    static {
+        try {
+            FormatFilterExtension.class.getClassLoader().loadClass("com.ibm.icu.text.MessageFormat");
+            hasIcuSupport = true;
+        } catch (ClassNotFoundException ex) {
+            LOG.trace("Initialize without ICU support: {}", ex.getMessage(), ex);
+            hasIcuSupport = false;
+        }
+    }
+
     @Override
     public Object call(final RenderContext renderContext, Object... arguments) {
         ExtensionUtils.checkArgumentCount(RuntimeExtension.FORMAT, arguments, 2);
@@ -67,15 +85,22 @@ public class FormatFilterExtension implements RuntimeExtension {
         String formattingType = runtimeObjectModel.toString(options.get(TYPE_OPTION));
         Object formatObject = options.get(FORMAT_OPTION);
         boolean hasPlaceHolders = PLACEHOLDER_REGEX.matcher(source).find();
+        // Check for complex placeholders only if no simple placeholders were found. If simple placeholders were found getFormattedString()
+        // is called anyways and the source can be matched complex placeholders later.
+        Boolean hasComplexPlaceholders = null;
+        if (!hasPlaceHolders && hasIcuSupport) {
+            hasComplexPlaceholders = COMPLEX_PLACEHOLDER_REGEX.matcher(source).find();
+            hasPlaceHolders = hasComplexPlaceholders;
+        }
         if (STRING_FORMAT_TYPE.equals(formattingType)) {
-            return getFormattedString(runtimeObjectModel, source, formatObject);
+            return getFormattedString(runtimeObjectModel, source, options, formatObject, hasComplexPlaceholders);
         } else if (DATE_FORMAT_TYPE.equals(formattingType) || (!hasPlaceHolders && runtimeObjectModel.isDate(formatObject))) {
             return getDateFormattedString(runtimeObjectModel, source, options, formatObject);
         } else if (NUMBER_FORMAT_TYPE.equals(formattingType) || (!hasPlaceHolders && runtimeObjectModel.isNumber(formatObject))) {
             return getNumberFormattedString(runtimeObjectModel, source, options, formatObject);
         }
         if (hasPlaceHolders) {
-            return getFormattedString(runtimeObjectModel, source, formatObject);
+            return getFormattedString(runtimeObjectModel, source, options, formatObject, hasComplexPlaceholders);
         }
         try {
             // somebody will hate me for this
@@ -91,11 +116,22 @@ public class FormatFilterExtension implements RuntimeExtension {
         } catch (IllegalArgumentException e) {
             // ignore
         }
-        return getFormattedString(runtimeObjectModel, source, formatObject);
+        return getFormattedString(runtimeObjectModel, source, options, formatObject, hasComplexPlaceholders);
     }
 
-    private Object getFormattedString(RuntimeObjectModel runtimeObjectModel, String source, Object formatObject) {
+    private Object getFormattedString(RuntimeObjectModel runtimeObjectModel, String source, Map<String, Object> options,
+                                      Object formatObject, Boolean hasComplexPlaceholders) {
         Object[] params = decodeParams(runtimeObjectModel, formatObject);
+        if (hasIcuSupport) {
+            if (hasComplexPlaceholders == null) {
+                hasComplexPlaceholders = COMPLEX_PLACEHOLDER_REGEX.matcher(source).find();
+            }
+            if (hasComplexPlaceholders) {
+                Locale locale = getLocale(runtimeObjectModel, options);
+                return formatStringIcu(source, locale, params);
+            }
+        }
+
         return formatString(runtimeObjectModel, source, params);
     }
 
@@ -169,6 +205,14 @@ public class FormatFilterExtension implements RuntimeExtension {
         return builder.toString();
     }
 
+    private String formatStringIcu(String source, Locale locale, Object[] params) {
+        MessageFormat messageFormat = new MessageFormat(source);
+        if (locale != null) {
+            messageFormat.setLocale(locale);
+        }
+        return messageFormat.format(params);
+    }
+
     private String toString(RuntimeObjectModel runtimeObjectModel, Object[] params, int index) {
         if (index >= 0 && index < params.length) {
             return runtimeObjectModel.toString(params[index]);
diff --git a/src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java b/src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java
new file mode 100644
index 0000000..63fd1e9
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.impl.engine.extension;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+import javax.script.Bindings;
+import javax.script.SimpleBindings;
+
+import org.apache.sling.scripting.sightly.render.AbstractRuntimeObjectModel;
+import org.apache.sling.scripting.sightly.render.RenderContext;
+import org.apache.sling.scripting.sightly.render.RuntimeObjectModel;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+import static org.junit.Assert.assertEquals;
+
+public class FormatFilterExtensionTest {
+
+    private final RenderContext renderContext = new RenderContext() {
+        @Override public RuntimeObjectModel getObjectModel() {
+            return new AbstractRuntimeObjectModel() {
+            };
+        }
+
+        @Override public Bindings getBindings() {
+            return new SimpleBindings();
+        }
+
+        @Override public Object call(String s, Object... objects) {
+            return null;
+        }
+    };
+    private final FormatFilterExtension subject = new FormatFilterExtension();
+
+    @Test
+    public void testSimpleFormat() {
+        Object result = subject.call(renderContext,
+            "This {0} a {1} format", ImmutableMap.of("format", Arrays.asList("is", "simple")));
+        assertEquals("This is a simple format", result);
+    }
+
+    @Test
+    public void testComplexFormatNoSimplePlaceholderWithLocale() {
+        Object result = subject.call(renderContext,
+            "This query has {0,plural,zero {# results} one {# result} other {# results}}",
+            new HashMap<String, Object>() {{
+                put("format", Arrays.asList(7));
+                put("locale", "en_US");
+            }});
+        assertEquals("This query has 7 results", result);
+    }
+
+    @Test
+    public void testComplexFormatWithSimplePlaceholderNoLocale() {
+        Object result = subject.call(renderContext,
+            "This {0} has {1,plural,zero {# results} one {# result} other {# results}}",
+            ImmutableMap.of("format", Arrays.asList("query", 7)));
+        assertEquals("This query has 7 results", result);
+    }
+}