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

markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 2fcb104294b2676154cb08f00d5665d668792280
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu Jul 8 22:02:26 2021 +0100

    Add support for coercing LambdaExpression to any functional interface
    
    This addresses this currently open issue against the EL spec
    https://github.com/eclipse-ee4j/el-ri/issues/45
    This is an initial implementation so users can provide feedback
    The implementation was inspired by rmuller's suggestion for an
    ELResolver that performs a similar function:
    https://stackoverflow.com/questions/46573761
---
 java/org/apache/el/LocalStrings.properties |  2 +
 java/org/apache/el/lang/ELSupport.java     | 27 +++++++++++
 test/org/apache/el/lang/TestELSupport.java | 76 ++++++++++++++++++++++++++++++
 webapps/docs/changelog.xml                 |  5 ++
 4 files changed, 110 insertions(+)

diff --git a/java/org/apache/el/LocalStrings.properties 
b/java/org/apache/el/LocalStrings.properties
index 1bf600d..d9f5503 100644
--- a/java/org/apache/el/LocalStrings.properties
+++ b/java/org/apache/el/LocalStrings.properties
@@ -13,6 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+elSupport.coerce.nonAbstract=Unable to coerce a LambdaExpression to the 
functional interface [{0}] because the method [{1}] is not abstract
+
 # General Errors
 error.cannotSetVariables=Cannot set variables on factory
 error.convert=Cannot convert [{0}] of type [{1}] to [{2}]
diff --git a/java/org/apache/el/lang/ELSupport.java 
b/java/org/apache/el/lang/ELSupport.java
index 808ad79..c37fbac 100644
--- a/java/org/apache/el/lang/ELSupport.java
+++ b/java/org/apache/el/lang/ELSupport.java
@@ -19,6 +19,9 @@ package org.apache.el.lang;
 import java.beans.PropertyEditor;
 import java.beans.PropertyEditorManager;
 import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.security.AccessController;
@@ -29,6 +32,7 @@ import java.util.Set;
 
 import jakarta.el.ELContext;
 import jakarta.el.ELException;
+import jakarta.el.LambdaExpression;
 
 import org.apache.el.util.MessageFactory;
 
@@ -588,6 +592,11 @@ public class ELSupport {
             return result;
         }
 
+        if (obj instanceof LambdaExpression && 
type.getAnnotation(FunctionalInterface.class) != null) {
+            T result = coerceToFunctionalInterface(ctx, (LambdaExpression) 
obj, type);
+            return result;
+        }
+
         throw new ELException(MessageFactory.get("error.convert",
                 obj, obj.getClass(), type));
     }
@@ -613,6 +622,24 @@ public class ELSupport {
         return result;
     }
 
+
+    private static <T> T coerceToFunctionalInterface(final ELContext ctx, 
final LambdaExpression lambdaExpression,
+            final Class<T> type) {
+        // Create a dynamic proxy for the functional interface
+        @SuppressWarnings("unchecked")
+        T result = (T) Proxy.newProxyInstance(type.getClassLoader(), new 
Class[] { type },
+                (Object obj, Method method, Object[] args) -> {
+            // Functional interfaces have a single, abstract method
+            if (!Modifier.isAbstract(method.getModifiers())) {
+                // TODO
+                throw new 
ELException(MessageFactory.get("elSupport.coerce.nonAbstract", type, method));
+            }
+            return lambdaExpression.invoke(ctx, args);
+        });
+        return result;
+    }
+
+
     public static final boolean isBigDecimalOp(final Object obj0,
             final Object obj1) {
         return (obj0 instanceof BigDecimal || obj1 instanceof BigDecimal);
diff --git a/test/org/apache/el/lang/TestELSupport.java 
b/test/org/apache/el/lang/TestELSupport.java
index 84d3ed4..672afd5 100644
--- a/test/org/apache/el/lang/TestELSupport.java
+++ b/test/org/apache/el/lang/TestELSupport.java
@@ -19,9 +19,11 @@ package org.apache.el.lang;
 import java.beans.PropertyEditorManager;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.function.Predicate;
 
 import jakarta.el.ELException;
 import jakarta.el.ELManager;
+import jakarta.el.ELProcessor;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -276,4 +278,78 @@ public class TestELSupport {
         VALB1,
         VALB2
     }
+
+
+    @Test
+    public void testCoercetoFunctionalInterface01() throws Exception {
+        final ELProcessor elp = new ELProcessor();
+        elp.defineFunction("", "", "org.apache.el.lang.TestELSupport", 
"testPredicateA");
+        Object result = elp.eval("testPredicateA(x -> x.equals('data'))");
+        Assert.assertEquals("PASS", result);
+    }
+
+
+    @Test
+    public void testCoercetoFunctionalInterface02() throws Exception {
+        final ELProcessor elp = new ELProcessor();
+        elp.defineFunction("", "", "org.apache.el.lang.TestELSupport", 
"testPredicateA");
+        Object result = elp.eval("testPredicateA(x -> !x.equals('data'))");
+        Assert.assertEquals("BLOCK", result);
+    }
+
+
+    @Test
+    public void testCoercetoFunctionalInterface03() throws Exception {
+        final ELProcessor elp = new ELProcessor();
+        elp.defineFunction("", "", "org.apache.el.lang.TestELSupport", 
"testPredicateB");
+        Object result = elp.eval("testPredicateB(x -> x > 50)");
+        Assert.assertEquals("PASS", result);
+    }
+
+
+    @Test
+    public void testCoercetoFunctionalInterface04() throws Exception {
+        final ELProcessor elp = new ELProcessor();
+        elp.defineFunction("", "", "org.apache.el.lang.TestELSupport", 
"testPredicateB");
+        Object result = elp.eval("testPredicateB(x -> x < 50)");
+        Assert.assertEquals("BLOCK", result);
+    }
+
+
+    @Test(expected = ELException.class)
+    public void testCoercetoFunctionalInterface05() throws Exception {
+        final ELProcessor elp = new ELProcessor();
+        elp.defineFunction("", "", "org.apache.el.lang.TestELSupport", 
"testPredicateC");
+        elp.eval("testPredicateC(x -> x > 50)");
+    }
+
+
+    public static String testPredicateA(Predicate<String> filter) {
+        String s = "data";
+        if (filter.test(s)) {
+            return "PASS";
+        } else {
+            return "BLOCK";
+        }
+    }
+
+
+    public static String testPredicateB(Predicate<Long> filter) {
+        Long l = Long.valueOf(100);
+        if (filter.test(l)) {
+            return "PASS";
+        } else {
+            return "BLOCK";
+        }
+    }
+
+
+    public static String testPredicateC(Predicate<String> filter) {
+        String s = "text";
+        if (filter.test(s)) {
+            return "PASS";
+        } else {
+            return "BLOCK";
+        }
+    }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 2e78aa9..4cec1f7 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -148,6 +148,11 @@
         Add additional generics to the EL API to align with the latest changes
         in the EL specification project. (markt)
       </scode>
+      <add>
+        Enable EL lambda expressions to be coerced to functional interfaces.
+        This is an implementation of a proposed extension to the Jakarta
+        Expression Language specification. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Web applications">

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to