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 fda4faa Add MethodReference support fda4faa is described below commit fda4faaaa16233c2d32961f4765b2dd9eb1f4a4c Author: Mark Thomas <ma...@apache.org> AuthorDate: Wed Sep 8 16:50:41 2021 +0100 Add MethodReference support --- java/jakarta/el/MethodExpression.java | 27 +++++++ java/jakarta/el/MethodReference.java | 85 ++++++++++++++++++++++ java/org/apache/el/MethodExpressionImpl.java | 11 +++ java/org/apache/el/MethodExpressionLiteral.java | 27 ++++++- java/org/apache/el/parser/AstIdentifier.java | 6 ++ java/org/apache/el/parser/AstValue.java | 20 +++++ java/org/apache/el/parser/Node.java | 6 ++ java/org/apache/el/parser/SimpleNode.java | 5 ++ java/org/apache/jasper/el/JspMethodExpression.java | 26 +++++++ test/jakarta/el/TestMethodReference.java | 68 +++++++++++++++++ test/jakarta/el/TesterBean.java | 7 ++ webapps/docs/changelog.xml | 5 ++ 12 files changed, 291 insertions(+), 2 deletions(-) diff --git a/java/jakarta/el/MethodExpression.java b/java/jakarta/el/MethodExpression.java index d0ba8b4..2635960 100644 --- a/java/jakarta/el/MethodExpression.java +++ b/java/jakarta/el/MethodExpression.java @@ -64,4 +64,31 @@ public abstract class MethodExpression extends Expression { // Expected to be over-ridden by implementation return false; } + + /** + * Obtain the {@link MethodReference} for the method to which this method + * expression resolves. + * + * @param context The EL context for this evaluation + * + * @return This default implementation always returns <code>null</code> + * + * @throws NullPointerException + * If the supplied context is <code>null</code> + * @throws PropertyNotFoundException + * If a property/variable resolution failed because no match + * was found or a match was found but was not readable + * @throws MethodNotFoundException + * If no matching method can be found + * @throws ELException + * Wraps any exception throw whilst resolving the property + * + * @since EL 5.0 + */ + public MethodReference getMethodReference(ELContext context) { + // Expected to be over-ridden by implementation + context.notifyBeforeEvaluation(getExpressionString()); + context.notifyAfterEvaluation(getExpressionString()); + return null; + } } diff --git a/java/jakarta/el/MethodReference.java b/java/jakarta/el/MethodReference.java new file mode 100644 index 0000000..b7855c6 --- /dev/null +++ b/java/jakarta/el/MethodReference.java @@ -0,0 +1,85 @@ +/* + * 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 jakarta.el; + +import java.lang.annotation.Annotation; + +/** + * Provides information about the method to which a method expression resolves. + */ +public class MethodReference { + + private final Object base; + private final MethodInfo methodInfo; + private final Annotation[] annotations; + private final Object[] evaluatedParameters; + + + public MethodReference(Object base, MethodInfo methodInfo, Annotation[] annotations, Object[] evaluatedParameters) { + this.base = base; + this.methodInfo = methodInfo; + this.annotations = annotations; + this.evaluatedParameters = evaluatedParameters; + } + + + /** + * Obtain the base object on which the method will be invoked. + * + * @return The base object on which the method will be invoked or + * {@code null} for literal method expressions. + */ + public Object getBase() { + return base; + } + + + /** + * Obtain the {@link MethodInfo} for the {@link MethodExpression} for which + * this {@link MethodReference} has been generated. + * + * @return The {@link MethodInfo} for the {@link MethodExpression} for which + * this {@link MethodReference} has been generated. + */ + public MethodInfo getMethodInfo() { + return this.methodInfo; + } + + + /** + * Obtain the annotations on the method to which the associated expression + * resolves. + * + * @return The annotations on the method to which the associated expression + * resolves. If the are no annotations, then an empty array is + * returned. + */ + public Annotation[] getAnnotations() { + return annotations; + } + + + /** + * Obtain the evaluated parameter values that will be passed to the method + * to which the associated expression resolves. + * + * @return The evaluated parameters. + */ + public Object[] getEvaluatedParameters() { + return evaluatedParameters; + } +} diff --git a/java/org/apache/el/MethodExpressionImpl.java b/java/org/apache/el/MethodExpressionImpl.java index 2acc78e..539bdca 100644 --- a/java/org/apache/el/MethodExpressionImpl.java +++ b/java/org/apache/el/MethodExpressionImpl.java @@ -27,6 +27,7 @@ import jakarta.el.FunctionMapper; import jakarta.el.MethodExpression; import jakarta.el.MethodInfo; import jakarta.el.MethodNotFoundException; +import jakarta.el.MethodReference; import jakarta.el.PropertyNotFoundException; import jakarta.el.VariableMapper; @@ -315,4 +316,14 @@ public final class MethodExpressionImpl extends MethodExpression implements public boolean isParametersProvided() { return this.getNode().isParametersProvided(); } + + + @Override + public MethodReference getMethodReference(ELContext context) { + EvaluationContext ctx = new EvaluationContext(context, this.fnMapper, this.varMapper); + ctx.notifyBeforeEvaluation(getExpressionString()); + MethodReference methodReference = this.getNode().getMethodReference(ctx); + ctx.notifyAfterEvaluation(getExpressionString()); + return methodReference; + } } diff --git a/java/org/apache/el/MethodExpressionLiteral.java b/java/org/apache/el/MethodExpressionLiteral.java index f71d38f..9fd0046 100644 --- a/java/org/apache/el/MethodExpressionLiteral.java +++ b/java/org/apache/el/MethodExpressionLiteral.java @@ -20,17 +20,23 @@ import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import java.lang.annotation.Annotation; import jakarta.el.ELContext; import jakarta.el.ELException; import jakarta.el.MethodExpression; import jakarta.el.MethodInfo; +import jakarta.el.MethodReference; +import org.apache.el.util.MessageFactory; import org.apache.el.util.ReflectionUtil; public class MethodExpressionLiteral extends MethodExpression implements Externalizable { + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + private Class<?> expectedType; private String expr; @@ -51,12 +57,16 @@ public class MethodExpressionLiteral extends MethodExpression implements Externa @Override public MethodInfo getMethodInfo(ELContext context) throws ELException { context.notifyBeforeEvaluation(getExpressionString()); - MethodInfo result = - new MethodInfo(this.expr, this.expectedType, this.paramTypes); + MethodInfo result = getMethodInfoInternal(); context.notifyAfterEvaluation(getExpressionString()); return result; } + + private MethodInfo getMethodInfoInternal() throws ELException { + return new MethodInfo(this.expr, this.expectedType, this.paramTypes); + } + @Override public Object invoke(ELContext context, Object[] params) throws ELException { context.notifyBeforeEvaluation(getExpressionString()); @@ -70,6 +80,19 @@ public class MethodExpressionLiteral extends MethodExpression implements Externa return result; } + + @Override + public MethodReference getMethodReference(ELContext context) { + if (context == null) { + throw new NullPointerException(MessageFactory.get("error.context.null")); + } + context.notifyBeforeEvaluation(getExpressionString()); + MethodReference result = + new MethodReference(null, getMethodInfoInternal(), EMPTY_ANNOTATION_ARRAY, EMPTY_OBJECT_ARRAY); + context.notifyAfterEvaluation(getExpressionString()); + return result; + } + @Override public String getExpressionString() { return this.expr; diff --git a/java/org/apache/el/parser/AstIdentifier.java b/java/org/apache/el/parser/AstIdentifier.java index 671b711..237604c 100644 --- a/java/org/apache/el/parser/AstIdentifier.java +++ b/java/org/apache/el/parser/AstIdentifier.java @@ -22,6 +22,7 @@ import jakarta.el.ELException; import jakarta.el.MethodExpression; import jakarta.el.MethodInfo; import jakarta.el.MethodNotFoundException; +import jakarta.el.MethodReference; import jakarta.el.PropertyNotFoundException; import jakarta.el.ValueExpression; import jakarta.el.ValueReference; @@ -171,6 +172,11 @@ public final class AstIdentifier extends SimpleNode { } @Override + public MethodReference getMethodReference(EvaluationContext ctx) { + return this.getMethodExpression(ctx).getMethodReference(ctx.getELContext()); + } + + @Override public void setImage(String image) { if (!Validation.isIdentifier(image)) { throw new ELException(MessageFactory.get("error.identifier.notjava", diff --git a/java/org/apache/el/parser/AstValue.java b/java/org/apache/el/parser/AstValue.java index 7797e81..d926867 100644 --- a/java/org/apache/el/parser/AstValue.java +++ b/java/org/apache/el/parser/AstValue.java @@ -25,6 +25,7 @@ import jakarta.el.ELException; import jakarta.el.ELResolver; import jakarta.el.LambdaExpression; import jakarta.el.MethodInfo; +import jakarta.el.MethodReference; import jakarta.el.PropertyNotFoundException; import jakarta.el.ValueReference; @@ -259,6 +260,25 @@ public final class AstValue extends SimpleNode { return result; } + @Override + public MethodReference getMethodReference(EvaluationContext ctx) { + Target t = getTarget(ctx); + Method m = null; + Object[] values = null; + Class<?>[] types = null; + if (isParametersProvided()) { + values = ((AstMethodParameters) this.jjtGetChild( + this.jjtGetNumChildren() - 1)).getParameters(ctx); + types = getTypesFromValues(values); + } + m = ReflectionUtil.getMethod(ctx, t.base, t.property, types, values); + + // Handle varArgs and any coercion required + values = convertArgs(ctx, values, m); + + return new MethodReference(t.base, getMethodInfo(ctx, types), m.getAnnotations(), values); + } + private Object[] convertArgs(EvaluationContext ctx, Object[] src, Method m) { Class<?>[] types = m.getParameterTypes(); if (types.length == 0) { diff --git a/java/org/apache/el/parser/Node.java b/java/org/apache/el/parser/Node.java index 0cf4a18..ec7e86a 100644 --- a/java/org/apache/el/parser/Node.java +++ b/java/org/apache/el/parser/Node.java @@ -20,6 +20,7 @@ package org.apache.el.parser; import jakarta.el.ELException; import jakarta.el.MethodInfo; +import jakarta.el.MethodReference; import jakarta.el.ValueReference; import org.apache.el.lang.EvaluationContext; @@ -80,4 +81,9 @@ public interface Node { * @since EL 2.2 */ public boolean isParametersProvided(); + + /** + * @since EL 5.0 + */ + public MethodReference getMethodReference(EvaluationContext ctx); } diff --git a/java/org/apache/el/parser/SimpleNode.java b/java/org/apache/el/parser/SimpleNode.java index b50645a..a698515 100644 --- a/java/org/apache/el/parser/SimpleNode.java +++ b/java/org/apache/el/parser/SimpleNode.java @@ -21,6 +21,7 @@ import java.util.Arrays; import jakarta.el.ELException; import jakarta.el.MethodInfo; +import jakarta.el.MethodReference; import jakarta.el.PropertyNotWritableException; import jakarta.el.ValueReference; @@ -157,6 +158,10 @@ public abstract class SimpleNode implements Node { throw new UnsupportedOperationException(); } + @Override + public MethodReference getMethodReference(EvaluationContext ctx) { + throw new UnsupportedOperationException(); + } @Override public int hashCode() { diff --git a/java/org/apache/jasper/el/JspMethodExpression.java b/java/org/apache/jasper/el/JspMethodExpression.java index c59b6f0..6e336f6 100644 --- a/java/org/apache/jasper/el/JspMethodExpression.java +++ b/java/org/apache/jasper/el/JspMethodExpression.java @@ -26,6 +26,7 @@ import jakarta.el.ELException; import jakarta.el.MethodExpression; import jakarta.el.MethodInfo; import jakarta.el.MethodNotFoundException; +import jakarta.el.MethodReference; import jakarta.el.PropertyNotFoundException; public final class JspMethodExpression extends MethodExpression implements @@ -99,6 +100,31 @@ public final class JspMethodExpression extends MethodExpression implements } @Override + public MethodReference getMethodReference(ELContext context) { + context.notifyBeforeEvaluation(getExpressionString()); + try { + MethodReference result = this.target.getMethodReference(context); + context.notifyAfterEvaluation(getExpressionString()); + return result; + } catch (MethodNotFoundException e) { + if (e instanceof JspMethodNotFoundException) { + throw e; + } + throw new JspMethodNotFoundException(this.mark, e); + } catch (PropertyNotFoundException e) { + if (e instanceof JspPropertyNotFoundException) { + throw e; + } + throw new JspPropertyNotFoundException(this.mark, e); + } catch (ELException e) { + if (e instanceof JspELException) { + throw e; + } + throw new JspELException(this.mark, e); + } + } + + @Override public boolean equals(Object obj) { return this.target.equals(obj); } diff --git a/test/jakarta/el/TestMethodReference.java b/test/jakarta/el/TestMethodReference.java new file mode 100644 index 0000000..c9b524f --- /dev/null +++ b/test/jakarta/el/TestMethodReference.java @@ -0,0 +1,68 @@ +/* + * 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 jakarta.el; + +import java.beans.BeanProperty; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.jasper.el.ELContextImpl; + +public class TestMethodReference { + + @Test + public void testGetAnnotationInfo01() { + // None + ExpressionFactory factory = ExpressionFactory.newInstance(); + ELContext context = new ELContextImpl(factory); + + TesterBean bean = new TesterBean("myBean"); + + ValueExpression var = factory.createValueExpression(bean, TesterBean.class); + context.getVariableMapper().setVariable("bean", var); + + MethodExpression me = factory.createMethodExpression(context, "${bean.getName()}", String.class, null); + + MethodReference result = me.getMethodReference(context); + + Assert.assertNotNull(result); + Assert.assertNotNull(result.getAnnotations()); + Assert.assertEquals(0, result.getAnnotations().length); + } + + @Test + public void testGetAnnotationInfo02() { + // @BeanProperty with defaults + ExpressionFactory factory = ExpressionFactory.newInstance(); + ELContext context = new ELContextImpl(factory); + + TesterBean bean = new TesterBean("myBean"); + + ValueExpression var = factory.createValueExpression(bean, TesterBean.class); + context.getVariableMapper().setVariable("bean", var); + + MethodExpression me = factory.createMethodExpression(context, "${bean.getValueD()}", String.class, null); + + MethodReference result = me.getMethodReference(context); + + Assert.assertNotNull(result); + Assert.assertNotNull(result.getAnnotations()); + Assert.assertEquals(1, result.getAnnotations().length); + Assert.assertEquals(BeanProperty.class, result.getAnnotations()[0].annotationType()); + } +} diff --git a/test/jakarta/el/TesterBean.java b/test/jakarta/el/TesterBean.java index e781476..8f0d1e4 100644 --- a/test/jakarta/el/TesterBean.java +++ b/test/jakarta/el/TesterBean.java @@ -16,6 +16,8 @@ */ package jakarta.el; +import java.beans.BeanProperty; + public class TesterBean { private String name; @@ -62,4 +64,9 @@ public class TesterBean { public Integer[] getValueC() { return valueC; } + + @BeanProperty + public String getValueD() { + return ""; + } } diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index be86080..86bbbc6 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -116,6 +116,11 @@ Tomcat with recent updates in the Jakarta EL specification project. (markt) </add> + <add> + Add support for <code>MethodReference</code> and the associated getter + on <code>MehtodExpression</code> to align Tomcat with recent updates in + the Jakarta EL specification project. (markt) + </add> </changelog> </subsection> </section> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org