| /* |
| * 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.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| /** |
| * @since EL 3.0 |
| */ |
| public class ELProcessor { |
| |
| private static final Set<String> PRIMITIVES = new HashSet<>(); |
| static { |
| PRIMITIVES.add("boolean"); |
| PRIMITIVES.add("byte"); |
| PRIMITIVES.add("char"); |
| PRIMITIVES.add("double"); |
| PRIMITIVES.add("float"); |
| PRIMITIVES.add("int"); |
| PRIMITIVES.add("long"); |
| PRIMITIVES.add("short"); |
| } |
| |
| private static final String[] EMPTY_STRING_ARRAY = new String[0]; |
| |
| private final ELManager manager = new ELManager(); |
| private final ELContext context = manager.getELContext(); |
| private final ExpressionFactory factory = ELManager.getExpressionFactory(); |
| |
| |
| public ELManager getELManager() { |
| return manager; |
| } |
| |
| |
| public <T> T eval(String expression) { |
| @SuppressWarnings("unchecked") |
| T result = (T) getValue(expression, Object.class); |
| return result; |
| } |
| |
| |
| public <T> T getValue(String expression, Class<T> expectedType) { |
| ValueExpression ve = factory.createValueExpression(context, bracket(expression), expectedType); |
| return ve.getValue(context); |
| } |
| |
| |
| public void setValue(String expression, Object value) { |
| ValueExpression ve = factory.createValueExpression(context, bracket(expression), Object.class); |
| ve.setValue(context, value); |
| } |
| |
| |
| public void setVariable(String variable, String expression) { |
| if (expression == null) { |
| manager.setVariable(variable, null); |
| } else { |
| ValueExpression ve = factory.createValueExpression(context, bracket(expression), Object.class); |
| manager.setVariable(variable, ve); |
| } |
| } |
| |
| |
| public void defineFunction(String prefix, String function, String className, String methodName) |
| throws ClassNotFoundException, NoSuchMethodException { |
| |
| if (prefix == null || function == null || className == null || methodName == null) { |
| throw new NullPointerException(Util.message(context, "elProcessor.defineFunctionNullParams")); |
| } |
| |
| // Check the imports |
| Class<?> clazz = context.getImportHandler().resolveClass(className); |
| |
| if (clazz == null) { |
| clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader()); |
| } |
| |
| if (!Modifier.isPublic(clazz.getModifiers())) { |
| throw new ClassNotFoundException( |
| Util.message(context, "elProcessor.defineFunctionInvalidClass", className)); |
| } |
| |
| MethodSignature sig = new MethodSignature(context, methodName, className); |
| |
| if (function.isEmpty()) { |
| function = sig.getName(); |
| } |
| |
| // Only returns public methods. Module access is checked below. |
| Method[] methods = clazz.getMethods(); |
| |
| for (Method method : methods) { |
| if (!Modifier.isStatic(method.getModifiers())) { |
| continue; |
| } |
| if (!Util.canAccess(null, method)) { |
| continue; |
| } |
| if (method.getName().equals(sig.getName())) { |
| if (sig.getParamTypeNames() == null) { |
| // Only a name provided, no signature so map the first |
| // method declared |
| manager.mapFunction(prefix, function, method); |
| return; |
| } |
| if (sig.getParamTypeNames().length != method.getParameterTypes().length) { |
| continue; |
| } |
| if (sig.getParamTypeNames().length == 0) { |
| manager.mapFunction(prefix, function, method); |
| return; |
| } else { |
| Class<?>[] types = method.getParameterTypes(); |
| String[] typeNames = sig.getParamTypeNames(); |
| boolean match = true; |
| for (int i = 0; i < types.length; i++) { |
| if (i == types.length - 1 && method.isVarArgs()) { |
| String typeName = typeNames[i]; |
| if (typeName.endsWith("...")) { |
| typeName = typeName.substring(0, typeName.length() - 3); |
| if (!typeName.equals(types[i].getName())) { |
| match = false; |
| } |
| } else { |
| match = false; |
| } |
| } else if (!types[i].getName().equals(typeNames[i])) { |
| match = false; |
| break; |
| } |
| } |
| if (match) { |
| manager.mapFunction(prefix, function, method); |
| return; |
| } |
| } |
| } |
| } |
| |
| throw new NoSuchMethodException( |
| Util.message(context, "elProcessor.defineFunctionNoMethod", methodName, className)); |
| } |
| |
| |
| /** |
| * Map a method to a function name. |
| * |
| * @param prefix Function prefix |
| * @param function Function name |
| * @param method Method |
| * |
| * @throws NullPointerException If any of the arguments are null |
| * @throws NoSuchMethodException If the method is not static |
| */ |
| public void defineFunction(String prefix, String function, Method method) throws NoSuchMethodException { |
| |
| if (prefix == null || function == null || method == null) { |
| throw new NullPointerException(Util.message(context, "elProcessor.defineFunctionNullParams")); |
| } |
| |
| int modifiers = method.getModifiers(); |
| |
| // Check for static, public method and module access |
| if (!Modifier.isStatic(modifiers) || !Util.canAccess(null, method)) { |
| throw new NoSuchMethodException(Util.message(context, "elProcessor.defineFunctionInvalidMethod", |
| method.getName(), method.getDeclaringClass().getName())); |
| } |
| |
| manager.mapFunction(prefix, function, method); |
| } |
| |
| |
| public void defineBean(String name, Object bean) { |
| manager.defineBean(name, bean); |
| } |
| |
| |
| private static String bracket(String expression) { |
| return "${" + expression + "}"; |
| } |
| |
| private static class MethodSignature { |
| |
| private final String name; |
| private final String[] parameterTypeNames; |
| |
| MethodSignature(ELContext context, String methodName, String className) throws NoSuchMethodException { |
| |
| int paramIndex = methodName.indexOf('('); |
| |
| if (paramIndex == -1) { |
| name = methodName.trim(); |
| parameterTypeNames = null; |
| } else { |
| String returnTypeAndName = methodName.substring(0, paramIndex).trim(); |
| // Assume that the return type and the name are separated by |
| // whitespace. Given the use of trim() above, there should only |
| // be one sequence of whitespace characters. |
| int wsPos = -1; |
| for (int i = 0; i < returnTypeAndName.length(); i++) { |
| if (Character.isWhitespace(returnTypeAndName.charAt(i))) { |
| wsPos = i; |
| break; |
| } |
| } |
| if (wsPos == -1) { |
| throw new NoSuchMethodException(); |
| } |
| name = returnTypeAndName.substring(wsPos).trim(); |
| |
| String paramString = methodName.substring(paramIndex).trim(); |
| // We know the params start with '(', check they end with ')' |
| if (!paramString.endsWith(")")) { |
| throw new NoSuchMethodException(Util.message(context, |
| "elProcessor.defineFunctionInvalidParameterList", paramString, methodName, className)); |
| } |
| // Trim '(' and ')' |
| paramString = paramString.substring(1, paramString.length() - 1).trim(); |
| if (paramString.isEmpty()) { |
| parameterTypeNames = EMPTY_STRING_ARRAY; |
| } else { |
| parameterTypeNames = paramString.split(","); |
| ImportHandler importHandler = context.getImportHandler(); |
| for (int i = 0; i < parameterTypeNames.length; i++) { |
| String parameterTypeName = parameterTypeNames[i].trim(); |
| int dimension = 0; |
| int bracketPos = parameterTypeName.indexOf('['); |
| if (bracketPos > -1) { |
| String parameterTypeNameOnly = parameterTypeName.substring(0, bracketPos).trim(); |
| while (bracketPos > -1) { |
| dimension++; |
| bracketPos = parameterTypeName.indexOf('[', bracketPos + 1); |
| } |
| parameterTypeName = parameterTypeNameOnly; |
| } |
| boolean varArgs = false; |
| if (parameterTypeName.endsWith("...")) { |
| varArgs = true; |
| dimension = 1; |
| parameterTypeName = parameterTypeName.substring(0, parameterTypeName.length() - 3).trim(); |
| } |
| boolean isPrimitive = PRIMITIVES.contains(parameterTypeName); |
| if (isPrimitive && dimension > 0) { |
| // When in an array, class name changes for primitive |
| switch (parameterTypeName) { |
| case "boolean" -> parameterTypeName = "Z"; |
| case "byte" -> parameterTypeName = "B"; |
| case "char" -> parameterTypeName = "C"; |
| case "double" -> parameterTypeName = "D"; |
| case "float" -> parameterTypeName = "F"; |
| case "int" -> parameterTypeName = "I"; |
| case "long" -> parameterTypeName = "J"; |
| case "short" -> parameterTypeName = "S"; |
| default -> { |
| // Should never happen |
| } |
| } |
| } else if (!isPrimitive && !parameterTypeName.contains(".")) { |
| Class<?> clazz = importHandler.resolveClass(parameterTypeName); |
| if (clazz == null) { |
| throw new NoSuchMethodException( |
| Util.message(context, "elProcessor.defineFunctionInvalidParameterTypeName", |
| parameterTypeNames[i], methodName, className)); |
| } |
| parameterTypeName = clazz.getName(); |
| } |
| if (dimension > 0) { |
| // Convert to array form of class name |
| StringBuilder sb = new StringBuilder(); |
| sb.append("[".repeat(dimension)); |
| if (!isPrimitive) { |
| sb.append('L'); |
| } |
| sb.append(parameterTypeName); |
| if (!isPrimitive) { |
| sb.append(';'); |
| } |
| parameterTypeName = sb.toString(); |
| } |
| if (varArgs) { |
| parameterTypeName += "..."; |
| } |
| parameterTypeNames[i] = parameterTypeName; |
| } |
| } |
| } |
| |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * @return <code>null</code> if just the method name was specified, an empty List if an empty parameter list was |
| * specified - i.e. () - otherwise an ordered list of parameter type names |
| */ |
| public String[] getParamTypeNames() { |
| return parameterTypeNames; |
| } |
| } |
| } |