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


The following commit(s) were added to refs/heads/main by this push:
     new 0bc6e4f4c9 Fix LambdaExpression to functional interface coercion
0bc6e4f4c9 is described below

commit 0bc6e4f4c91804f1d2dd3102947393e928f5d58e
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Mar 21 15:19:07 2023 +0000

    Fix LambdaExpression to functional interface coercion
---
 java/jakarta/el/ELContext.java                  | 67 +++++++++++++++++++
 java/org/apache/el/lang/ELSupport.java          | 12 +++-
 test/org/apache/el/TestValueExpressionImpl.java | 86 +++++++++++++++++++++++++
 test/org/apache/el/TesterBeanJ.java             | 50 ++++++++++++++
 webapps/docs/changelog.xml                      |  9 +++
 5 files changed, 223 insertions(+), 1 deletion(-)

diff --git a/java/jakarta/el/ELContext.java b/java/jakarta/el/ELContext.java
index 0f6d221cc9..0e80de277f 100644
--- a/java/jakarta/el/ELContext.java
+++ b/java/jakarta/el/ELContext.java
@@ -16,6 +16,8 @@
  */
 package jakarta.el;
 
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -310,6 +312,71 @@ public abstract class ELContext {
             setPropertyResolved(originalResolved);
         }
 
+        if (obj instanceof LambdaExpression && isFunctionalInterface(type)) {
+            ((LambdaExpression) obj).setELContext(this);
+        }
+
         return ELManager.getExpressionFactory().coerceToType(obj, type);
     }
+
+
+    /*
+     * Copied from org.apache.el.lang.ELSupport - keep in sync
+     */
+    static boolean isFunctionalInterface(Class<?> type) {
+
+        if (!type.isInterface()) {
+            return false;
+        }
+
+        boolean foundAbstractMethod = false;
+        Method[] methods = type.getMethods();
+        for (Method method : methods) {
+            if (Modifier.isAbstract(method.getModifiers())) {
+                // Abstract methods that override one of the public methods
+                // of Object don't count
+                if (overridesObjectMethod(method)) {
+                    continue;
+                }
+                if (foundAbstractMethod) {
+                    // Found more than one
+                    return false;
+                } else {
+                    foundAbstractMethod = true;
+                }
+            }
+        }
+        return foundAbstractMethod;
+    }
+
+
+    /*
+     * Copied from org.apache.el.lang.ELSupport - keep in sync
+     */
+    private static boolean overridesObjectMethod(Method method) {
+        // There are three methods that can be overridden
+        if ("equals".equals(method.getName())) {
+            if (method.getReturnType().equals(boolean.class)) {
+                if (method.getParameterCount() == 1) {
+                    if (method.getParameterTypes()[0].equals(Object.class)) {
+                        return true;
+                    }
+                }
+            }
+        } else if ("hashCode".equals(method.getName())) {
+            if (method.getReturnType().equals(int.class)) {
+                if (method.getParameterCount() == 0) {
+                    return true;
+                }
+            }
+        } else if ("toString".equals(method.getName())) {
+            if (method.getReturnType().equals(String.class)) {
+                if (method.getParameterCount() == 0) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/java/org/apache/el/lang/ELSupport.java 
b/java/org/apache/el/lang/ELSupport.java
index d663450a4a..4aa4f4aa7e 100644
--- a/java/org/apache/el/lang/ELSupport.java
+++ b/java/org/apache/el/lang/ELSupport.java
@@ -619,7 +619,11 @@ public class ELSupport {
                     if (!Modifier.isAbstract(method.getModifiers())) {
                         throw new 
ELException(MessageFactory.get("elSupport.coerce.nonAbstract", type, method));
                     }
-                    return lambdaExpression.invoke(ctx, args);
+                    if (ctx == null) {
+                        return lambdaExpression.invoke(args);
+                    } else {
+                        return lambdaExpression.invoke(ctx, args);
+                    }
                 });
             return result;
         };
@@ -675,6 +679,9 @@ public class ELSupport {
     }
 
 
+    /*
+     * Copied to jakarta.el.ELContext - keep in sync
+     */
     static boolean isFunctionalInterface(Class<?> type) {
 
         if (!type.isInterface()) {
@@ -702,6 +709,9 @@ public class ELSupport {
     }
 
 
+    /*
+     * Copied to jakarta.el.ELContext - keep in sync
+     */
     private static boolean overridesObjectMethod(Method method) {
         // There are three methods that can be overridden
         if ("equals".equals(method.getName())) {
diff --git a/test/org/apache/el/TestValueExpressionImpl.java 
b/test/org/apache/el/TestValueExpressionImpl.java
index f238a89c5c..4577bf2b5b 100644
--- a/test/org/apache/el/TestValueExpressionImpl.java
+++ b/test/org/apache/el/TestValueExpressionImpl.java
@@ -21,6 +21,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import jakarta.el.ELBaseTest;
 import jakarta.el.ELContext;
@@ -242,4 +243,89 @@ public class TestValueExpressionImpl extends ELBaseTest {
 
         Assert.assertEquals("", beanB.getName());
     }
+
+
+    @Test
+    public void testOptional01() {
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        ELContext context = new ELContextImpl();
+
+        final String data = "some data";
+
+        TesterBeanJ beanJ = new TesterBeanJ();
+        TesterBeanJ beanJ2 = new TesterBeanJ();
+        beanJ2.setData(data);
+        beanJ.setBean(beanJ2);
+
+        ValueExpression var = factory.createValueExpression(beanJ, 
TesterBeanJ.class);
+        context.getVariableMapper().setVariable("beanJ", var);
+
+        ValueExpression ve = factory.createValueExpression(context, 
"${beanJ.optionalBean.map(b -> b.data)}",
+                Optional.class);
+
+        @SuppressWarnings("unchecked")
+        Optional<String> result = (Optional<String>) ve.getValue(context);
+        Assert.assertEquals(data, result.get());
+    }
+
+
+    @Test
+    public void testOptional02() {
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        ELContext context = new ELContextImpl();
+
+        TesterBeanJ beanJ = new TesterBeanJ();
+
+        ValueExpression var = factory.createValueExpression(beanJ, 
TesterBeanJ.class);
+        context.getVariableMapper().setVariable("beanJ", var);
+
+        ValueExpression ve = factory.createValueExpression(context, 
"${beanJ.optionalBean.map(b -> b.data)}",
+                Optional.class);
+
+        @SuppressWarnings("unchecked")
+        Optional<String> result = (Optional<String>) ve.getValue(context);
+        Assert.assertTrue(result.isEmpty());
+    }
+
+
+    @Test
+    public void testOptional03() {
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        ELContext context = new ELContextImpl();
+
+        final String data = "some data";
+
+        TesterBeanJ beanJ = new TesterBeanJ();
+        TesterBeanJ beanJ2 = new TesterBeanJ();
+        beanJ2.setData(data);
+        beanJ.setBean(beanJ2);
+
+        ValueExpression var = factory.createValueExpression(beanJ, 
TesterBeanJ.class);
+        context.getVariableMapper().setVariable("beanJ", var);
+
+        ValueExpression ve = factory.createValueExpression(context, 
"${beanJ.optionalBean.get().data}", String.class);
+
+        String result = (String) ve.getValue(context);
+        Assert.assertEquals(data, result);
+    }
+
+
+    @Test
+    public void testOptional04() {
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        ELContext context = new ELContextImpl();
+
+        TesterBeanJ beanJ = new TesterBeanJ();
+
+        ValueExpression var = factory.createValueExpression(beanJ, 
TesterBeanJ.class);
+        context.getVariableMapper().setVariable("beanJ", var);
+
+        ValueExpression ve = factory.createValueExpression(context,
+                "${beanJ.optionalBean.map(b -> b.data).orElse(null)}", 
String.class);
+
+        String result = (String) ve.getValue(context);
+        // Result is null but is coerced to String which makes it ""
+        Assert.assertNotNull(result);
+        Assert.assertTrue(result.isEmpty());
+    }
 }
diff --git a/test/org/apache/el/TesterBeanJ.java 
b/test/org/apache/el/TesterBeanJ.java
new file mode 100644
index 0000000000..da0c416403
--- /dev/null
+++ b/test/org/apache/el/TesterBeanJ.java
@@ -0,0 +1,50 @@
+/*
+ * 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.el;
+
+import java.util.Optional;
+
+public class TesterBeanJ {
+
+    private TesterBeanJ bean;
+    private String data;
+
+
+    public TesterBeanJ getBean() {
+        return bean;
+    }
+
+
+    public void setBean(TesterBeanJ bean) {
+        this.bean = bean;
+    }
+
+
+    public Optional<TesterBeanJ> getOptionalBean() {
+        return Optional.ofNullable(bean);
+    }
+
+
+    public String getData() {
+        return data;
+    }
+
+
+    public void setData(String data) {
+        this.data = data;
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index c7eb53e708..e88e2fdf22 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -187,6 +187,15 @@
       </fix>
     </changelog>
   </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        Fix bug that meant some instances of coercing a
+        <code>LambdaExpression</code> to a functional interface invocation
+        failed. (markt)
+      </fix>
+    </changelog>
+  </subsection>
   <subsection name="WebSocket">
     <changelog>
       <fix>


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

Reply via email to