Re: [tomcat] 03/03: Add support for coercing LambdaExpression to any functional interface
On 09/07/2021 11:40, Konstantin Kolinko wrote: пт, 9 июл. 2021 г. в 12:19, Mark Thomas : On 09/07/2021 09:58, Konstantin Kolinko wrote: Thanks Konstantin. This is good feedback. [...] I wonder how Java itself (a java compiler) deals with coercion of lambdas to interfaces. Either it generates calls to some helper API, or it just repeats the same boilerplate code over and over. I'm not sure. I did look to see if there was anything in the public API around this I could use to help but didn't find anything. I found some info: https://blogs.oracle.com/javamagazine/behind-the-scenes-how-do-lambda-expressions-really-work-in-java "Behind the scenes: How do lambda expressions really work in Java?" https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html From a quick read, it looks like it does not use reflection API, but a newer invocation API. I am not sure whether it is useful at this point. Just sharing. I looked at that but the parsed lambda expression from the EL isn't in the same form as a lambda expression in Java code would be in. You'd need to convert it. You might be able to use something like https://github.com/greenjoe/lambdaFromString to do this conversion. After looking at things like this for a while, I couldn't see a way to use them to create a solution that was an improvement on the Proxy approach. Mark - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
Re: [tomcat] 03/03: Add support for coercing LambdaExpression to any functional interface
пт, 9 июл. 2021 г. в 12:19, Mark Thomas : > > On 09/07/2021 09:58, Konstantin Kolinko wrote: > > Thanks Konstantin. This is good feedback. > > [...] > > > I wonder how Java itself (a java compiler) deals with coercion of > > lambdas to interfaces. Either it generates calls to some helper API, > > or it just repeats the same boilerplate code over and over. > > I'm not sure. I did look to see if there was anything in the public API > around this I could use to help but didn't find anything. I found some info: https://blogs.oracle.com/javamagazine/behind-the-scenes-how-do-lambda-expressions-really-work-in-java "Behind the scenes: How do lambda expressions really work in Java?" https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html >From a quick read, it looks like it does not use reflection API, but a newer invocation API. I am not sure whether it is useful at this point. Just sharing. Best regards, Konstantin Kolinko - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
Re: [tomcat] 03/03: Add support for coercing LambdaExpression to any functional interface
On 09/07/2021 09:58, Konstantin Kolinko wrote: Thanks Konstantin. This is good feedback. пт, 9 июл. 2021 г. в 00:07, Mark Thomas : Add support for coercing LambdaExpression to any functional interface The implementation for this turned out to be a lot simpler than I initially thought. I can;t help be think I've missed something. Please try and find ways to break this. If you do, feel free to add a disabled out test case and I'll take a look. Re: " if (obj instanceof LambdaExpression && type.getAnnotation(FunctionalInterface.class) != null) " 1. IIRC, using @FunctionalInterface annotation is a hint, not a requirement. E.g. at https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html "the compiler will treat any interface meeting the definition"... E.g. lambdas can be used with legacy code that does not use that annotation. I'll look into how easy it would be to handle the case where the annotation isn't present. I'm a little concerned about the overhead of determining if the functional interface definition is met but if that turns out to be an issue then some form of caching is likely to help. 2. Inheritance? If the annotation is applied not on this class or interface, but on some of its parents. Docs for "AnnotatedElement.getAnnotation(...)" - reading their definition of "present", I think that that method goes up a hierarchy of classes, but it does not go up a hierarchy of interfaces. https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/AnnotatedElement.html Example: a "SerializablePredicate" interface, https://vaadin.com/api/framework/8.13.2/com/vaadin/server/SerializablePredicate.html https://github.com/vaadin/framework/blob/master/server/src/main/java/com/vaadin/server/SerializablePredicate.java The SerializablePredicate interface does not redeclare @FunctionalInterface, but it is a functional interface. I'll look into that too. Re: "if (!Modifier.isAbstract(method.getModifiers())) { throw" 3. While only one method is abstract, other methods may be called: (a) methods defined by Object - equals(), hashCode() and toString(), (b) default methods defined in an interface. The current code throws an ELException. There is some sample code (though maybe not best) to deal with (a) at https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html I do not know how people deal with (b). It may be that InvocationHandler won't see those calls to default methods. (When I studied java proxies, that feature did not exist yet.) My thinking was that the proxy is only ever going to be used to map the LambdaExpression to the functional interface call. I couldn't see any way for any of those other methods to be called. Am I missing something? I wonder how Java itself (a java compiler) deals with coercion of lambdas to interfaces. Either it generates calls to some helper API, or it just repeats the same boilerplate code over and over. I'm not sure. I did look to see if there was anything in the public API around this I could use to help but didn't find anything. Thanks, Mark - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
Re: [tomcat] 03/03: Add support for coercing LambdaExpression to any functional interface
пт, 9 июл. 2021 г. в 00:07, Mark Thomas : > > On 08/07/2021 22:05, ma...@apache.org wrote: > > 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 > > AuthorDate: Thu Jul 8 22:02:26 2021 +0100 > > > > Add support for coercing LambdaExpression to any functional interface > > The implementation for this turned out to be a lot simpler than I > initially thought. I can;t help be think I've missed something. Please > try and find ways to break this. If you do, feel free to add a disabled > out test case and I'll take a look. Re: " if (obj instanceof LambdaExpression && type.getAnnotation(FunctionalInterface.class) != null) " 1. IIRC, using @FunctionalInterface annotation is a hint, not a requirement. E.g. at https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html "the compiler will treat any interface meeting the definition"... E.g. lambdas can be used with legacy code that does not use that annotation. 2. Inheritance? If the annotation is applied not on this class or interface, but on some of its parents. Docs for "AnnotatedElement.getAnnotation(...)" - reading their definition of "present", I think that that method goes up a hierarchy of classes, but it does not go up a hierarchy of interfaces. https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/AnnotatedElement.html Example: a "SerializablePredicate" interface, https://vaadin.com/api/framework/8.13.2/com/vaadin/server/SerializablePredicate.html https://github.com/vaadin/framework/blob/master/server/src/main/java/com/vaadin/server/SerializablePredicate.java The SerializablePredicate interface does not redeclare @FunctionalInterface, but it is a functional interface. > Re: "if (!Modifier.isAbstract(method.getModifiers())) { throw" 3. While only one method is abstract, other methods may be called: (a) methods defined by Object - equals(), hashCode() and toString(), (b) default methods defined in an interface. The current code throws an ELException. There is some sample code (though maybe not best) to deal with (a) at https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html I do not know how people deal with (b). It may be that InvocationHandler won't see those calls to default methods. (When I studied java proxies, that feature did not exist yet.) I wonder how Java itself (a java compiler) deals with coercion of lambdas to interfaces. Either it generates calls to some helper API, or it just repeats the same boilerplate code over and over. Best regards, Konstantin Kolinko - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
Re: [tomcat] 03/03: Add support for coercing LambdaExpression to any functional interface
On 08/07/2021 22:05, ma...@apache.org wrote: 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 AuthorDate: Thu Jul 8 22:02:26 2021 +0100 Add support for coercing LambdaExpression to any functional interface The implementation for this turned out to be a lot simpler than I initially thought. I can;t help be think I've missed something. Please try and find ways to break this. If you do, feel free to add a disabled out test case and I'll take a look. Thanks, Mark - To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org
[tomcat] 03/03: Add support for coercing LambdaExpression to any functional interface
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 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 coerceToFunctionalInterface(final ELContext ctx, final LambdaExpression lambdaExpression, +final Class 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 E