From 31f42ac4696ed810aebcddaddfabe923a14669dc Mon Sep 17 00:00:00 2001 From: Maximilian Hohndorf Date: Wed, 2 Apr 2025 13:21:59 +0200 Subject: [PATCH] Issue #11: Support of static functions with varargs Validating vararg-functions allow dynamic parameter count and invoking vararg-functions uses the existing parameter-resolution which already supports varargs. Also fixed an NPE in ReflectionUtil when providing null as vararg- parameter. --- .../glassfish/expressly/Messages.properties | 1 + .../expressly/lang/ExpressionBuilder.java | 7 +- .../expressly/parser/AstFunction.java | 12 ++-- .../expressly/util/ReflectionUtil.java | 3 +- .../glassfish/el/test/ELProcessorTest.java | 66 +++++++++++++++++++ 5 files changed, 81 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/glassfish/expressly/Messages.properties b/src/main/java/org/glassfish/expressly/Messages.properties index 802a38a..69a1f4d 100644 --- a/src/main/java/org/glassfish/expressly/Messages.properties +++ b/src/main/java/org/glassfish/expressly/Messages.properties @@ -51,6 +51,7 @@ error.property.notfound=Property ''{1}'' not found on {0} error.fnMapper.null=Expression uses functions, but no FunctionMapper was provided error.fnMapper.method=Function ''{0}'' not found error.fnMapper.paramcount=Function ''{0}'' specifies {1} params, but {2} were supplied +error.fnMapper.minparamcount=Function ''{0}'' requires at least {1} params, but {2} were supplied # **ExpressionImpl error.context.null=ELContext was null diff --git a/src/main/java/org/glassfish/expressly/lang/ExpressionBuilder.java b/src/main/java/org/glassfish/expressly/lang/ExpressionBuilder.java index e48eb35..29e459e 100644 --- a/src/main/java/org/glassfish/expressly/lang/ExpressionBuilder.java +++ b/src/main/java/org/glassfish/expressly/lang/ExpressionBuilder.java @@ -240,7 +240,12 @@ public void visit(Node node) throws ELException { int parameterCount = functionMethod.getParameterCount(); int argumentCount = ((AstMethodArguments) node.jjtGetChild(0)).getParameterCount(); - if (argumentCount != parameterCount) { + if(functionMethod.isVarArgs()) { + // last param of the method is the vararg -> 0..n vararg-arguments allowed + if (argumentCount < parameterCount - 1) { + throw new ELException(MessageFactory.get("error.fnMapper.minparamcount", funcNode.getOutputName(), parameterCount - 1, argumentCount)); + } + } else if (argumentCount != parameterCount) { throw new ELException(MessageFactory.get("error.fnMapper.paramcount", funcNode.getOutputName(), parameterCount, argumentCount)); } } else if (node instanceof AstIdentifier && varMapper != null) { diff --git a/src/main/java/org/glassfish/expressly/parser/AstFunction.java b/src/main/java/org/glassfish/expressly/parser/AstFunction.java index b8bd299..1206826 100644 --- a/src/main/java/org/glassfish/expressly/parser/AstFunction.java +++ b/src/main/java/org/glassfish/expressly/parser/AstFunction.java @@ -22,6 +22,7 @@ import org.glassfish.expressly.lang.EvaluationContext; import org.glassfish.expressly.util.MessageFactory; +import org.glassfish.expressly.util.ReflectionUtil; import jakarta.el.ELClass; import jakarta.el.ELException; @@ -167,12 +168,11 @@ public Object getValue(EvaluationContext ctx) throws ELException { Class[] paramTypes = functionMethod.getParameterTypes(); Object[] params = ((AstMethodArguments) this.children[0]).getParameters(ctx); Object result = null; - for (int i = 0; i < params.length; i++) { - try { - params[i] = ctx.convertToType(params[i], paramTypes[i]); - } catch (ELException ele) { - throw new ELException(MessageFactory.get("error.function", this.getOutputName()), ele); - } + try { + params = ReflectionUtil.buildParameters(ctx, paramTypes, functionMethod.isVarArgs(), params); + } + catch(ELException ele) { + throw new ELException(MessageFactory.get("error.function", this.getOutputName()), ele); } try { diff --git a/src/main/java/org/glassfish/expressly/util/ReflectionUtil.java b/src/main/java/org/glassfish/expressly/util/ReflectionUtil.java index 13e58c5..8dc2190 100644 --- a/src/main/java/org/glassfish/expressly/util/ReflectionUtil.java +++ b/src/main/java/org/glassfish/expressly/util/ReflectionUtil.java @@ -655,7 +655,8 @@ public static Object[] buildParameters(ELContext context, Class[] parameterTy } // Last parameter is the varargs - if (parameterTypes.length == paramCount && parameterTypes[varArgIndex] == params[varArgIndex].getClass()) { + if (parameterTypes.length == paramCount && + (params[varArgIndex] == null || parameterTypes[varArgIndex] == params[varArgIndex].getClass())) { parameters[varArgIndex] = params[varArgIndex]; } else { Class varArgClass = parameterTypes[varArgIndex].getComponentType(); diff --git a/src/test/java/org/glassfish/el/test/ELProcessorTest.java b/src/test/java/org/glassfish/el/test/ELProcessorTest.java index a1d484e..fd24fad 100644 --- a/src/test/java/org/glassfish/el/test/ELProcessorTest.java +++ b/src/test/java/org/glassfish/el/test/ELProcessorTest.java @@ -26,10 +26,12 @@ import jakarta.el.ELProcessor; import jakarta.el.ELManager; +import jakarta.el.ELException; import jakarta.el.ExpressionFactory; import jakarta.el.MethodExpression; import jakarta.el.ELContext; import java.lang.reflect.Method; +import java.util.Arrays; public class ELProcessorTest { @@ -160,6 +162,61 @@ public void defineFuncTest() { } assertTrue(caught); } + + @Test + public void defineVarargFuncTest() + { + Class c = MyBean.class; + Method meth1 = null; + Method meth2 = null; + try { + meth1 = c.getMethod("join", new Class[] {String[].class}); + meth2 = c.getMethod("joinPrefixed", new Class[] {String.class, String[].class}); + } catch(Exception e) { + System.out.printf("Exception: ", e); + } + try { + elp.defineFunction("xx", "", meth1); + String joined = elp.eval("xx:join()"); + assertEquals("", joined); + joined = elp.eval("xx:join('abc')"); + assertEquals("abc", joined); + joined = elp.eval("xx:join('abc','def')"); + assertEquals("abc,def", joined); + joined = elp.eval("xx:join('abc',null)"); // null is converted to empty string + assertEquals("abc,", joined); + joined = elp.eval("xx:join(null)"); // this is join((String[])null) in Java + assertEquals("", joined); + } catch(NoSuchMethodException ex) { + + } + + boolean caught = false; + try { + elp.defineFunction("xx", "", meth2); + Integer sum = elp.eval("xx:joinPrefixed()"); + assertEquals(0, sum.intValue()); + } catch (ELException ex) { + caught = true; + } catch (NoSuchMethodException ex) { + + } + assertTrue(caught); + + try { + elp.defineFunction("xx", "", meth2); + String joined = elp.eval("xx:joinPrefixed('res:')"); + assertEquals("res:", joined); + joined = elp.eval("xx:joinPrefixed('res:', 'abc')"); + assertEquals("res:abc", joined); + joined = elp.eval("xx:joinPrefixed('res:', 'abc', 'def')"); + assertEquals("res:abc,def", joined); + joined = elp.eval("xx:joinPrefixed('res:', null)"); + assertEquals("res:", joined); + } catch(NoSuchMethodException ex) { + + } + } /* @Test public void testBean() { @@ -200,6 +257,15 @@ public int getFoo(int i) { public static int getBar() { return 64; } + public static String join(String... args) { + if(args == null) + return ""; + + return String.join(",", args); + } + public static String joinPrefixed(String prefix, String... args) { + return prefix + join(args); + } } }