This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/10.1.x by this push: new c8601ff3a6 Fix LambdaExpression to functional interface coercion c8601ff3a6 is described below commit c8601ff3a6a6d6bdc9b5475ae902536ef5f87135 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 6e0c4c075e..aafb057041 100644 --- a/java/org/apache/el/lang/ELSupport.java +++ b/java/org/apache/el/lang/ELSupport.java @@ -635,7 +635,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; }; @@ -695,6 +699,9 @@ public class ELSupport { } + /* + * Copied to jakarta.el.ELContext - keep in sync + */ static boolean isFunctionalInterface(Class<?> type) { if (!type.isInterface()) { @@ -722,6 +729,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 408bd6f1b2..6eb447479c 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.ELContext; import jakarta.el.ExpressionFactory; @@ -241,4 +242,89 @@ public class TestValueExpressionImpl { 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 98b6e35879..d2dab6d75c 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -173,6 +173,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