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