All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jakarta.el.ELUtil Maven / Gradle / Ivy

/*
 * Copyright (c) 1997, 2022 Oracle and/or its affiliates and others.
 * All rights reserved.
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed 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.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 * Utility methods for this portion of the Jakarta Expression Language implementation
 *
 * 

* Methods on this class use a Map instance stored in ThreadLocal storage to minimize the performance impact on * operations that take place multiple times on a single Thread. The keys and values of the Map are implementation * private. * * @author edburns * @author Kin-man Chung * @author Dongbin Nie */ class ELUtil { /** * This class may not be constructed. */ private ELUtil() { } /** *

* The ThreadLocal variable used to record the jakarta.faces.context.FacesContext instance for * each processing thread. *

*/ private static ThreadLocal> instance = new ThreadLocal<>() { @Override protected Map initialValue() { return (null); } }; /** * @return a Map stored in ThreadLocal storage. This may be used by methods of this class to minimize the performance * impact for operations that may take place multiple times on a given Thread instance. */ private static Map getCurrentInstance() { Map result = instance.get(); if (result == null) { result = new HashMap<>(); setCurrentInstance(result); } return result; } /** * Replace the Map with the argument context. * * @param context the Map to be stored in ThreadLocal storage. */ private static void setCurrentInstance(Map context) { instance.set(context); } /** * Convenience method, calls through to getExceptionMessageString(ELContext,java.lang.String,Object []). * * @param context the ELContext from which the Locale for this message is extracted. * @param messageId the messageId String in the ResourceBundle * * @return a localized String for the argument messageId */ public static String getExceptionMessageString(ELContext context, String messageId) { return getExceptionMessageString(context, messageId, null); } /* *

Return a Localized message String suitable for use as an Exception message. Examine the argument * context for a Locale. If not present, use Locale.getDefault(). Load the * ResourceBundle "jakarta.el.Messages" using that locale. Get the message string for argument * messageId. If not found return "Missing Resource in Jakarta Expression Language implementation ??? messageId ???" with messageId * substituted with the runtime value of argument messageId. If found, and argument params is * non-null, format the message using the params. If formatting fails, return a sensible message including the * messageId. If argument params is null, skip formatting and return the message * directly, otherwise return the formatted message.

* * @param context the ELContext from which the Locale for this message is extracted. * * @param messageId the messageId String in the ResourceBundle * * @param params parameters to the message * * @return a localized String for the argument messageId */ public static String getExceptionMessageString(ELContext context, String messageId, Object[] params) { String result = ""; Locale locale = null; if (null == context || null == messageId) { return result; } if (null == (locale = context.getLocale())) { locale = Locale.getDefault(); } if (locale != null) { Map threadMap = getCurrentInstance(); ResourceBundle resourceBundle = null; if (null == (resourceBundle = threadMap.get(locale.toString()))) { resourceBundle = ResourceBundle.getBundle("jakarta.el.PrivateMessages", locale); threadMap.put(locale.toString(), resourceBundle); } if (null != resourceBundle) { try { result = resourceBundle.getString(messageId); if (null != params) { result = MessageFormat.format(result, params); } } catch (IllegalArgumentException iae) { result = "Can't get localized message: parameters to message appear to be incorrect. Message to format: " + messageId; } catch (MissingResourceException mre) { result = "Missing Resource in Jakarta Expression Language implementation: ???" + messageId + "???"; } catch (Exception e) { result = "Exception resolving message in Jakarta Expression Language implementation: ???" + messageId + "???"; } } } return result; } static Constructor findConstructor(Class klass, Class[] paramTypes, Object[] params) { String methodName = ""; if (klass == null) { throw new MethodNotFoundException("Method not found: " + klass + "." + methodName + "(" + paramString(paramTypes) + ")"); } if (paramTypes == null) { paramTypes = getTypesFromValues(params); } Constructor[] constructors = klass.getConstructors(); List wrappers = Wrapper.wrap(constructors); Wrapper result = findWrapper(klass, wrappers, methodName, paramTypes, params); if (result == null) { return null; } return getConstructor(klass, (Constructor) result.unWrap()); } static Object invokeConstructor(ELContext context, Constructor constructor, Object[] params) { Object[] parameters = buildParameters(context, constructor.getParameterTypes(), constructor.isVarArgs(), params); try { return constructor.newInstance(parameters); } catch (IllegalAccessException iae) { throw new ELException(iae); } catch (IllegalArgumentException iae) { throw new ELException(iae); } catch (InvocationTargetException ite) { throw new ELException(ite.getCause()); } catch (InstantiationException ie) { throw new ELException(ie.getCause()); } } static Method findMethod(Class klass, Object base, String methodName, Class[] paramTypes, Object[] params, boolean staticOnly) { Method method = findMethod(klass, base, methodName, paramTypes, params); if (staticOnly && !Modifier.isStatic(method.getModifiers())) { throw new MethodNotFoundException("Method " + methodName + "for class " + klass + " not found or accessible"); } return method; } static Object invokeMethod(ELContext context, Method method, Object base, Object[] params) { Object[] parameters = buildParameters(context, method.getParameterTypes(), method.isVarArgs(), params); try { return method.invoke(base, parameters); } catch (IllegalAccessException iae) { throw new ELException(iae); } catch (IllegalArgumentException iae) { throw new ELException(iae); } catch (InvocationTargetException ite) { throw new ELException(ite.getCause()); } } static Method findMethod(Class clazz, Object base, String methodName, Class[] paramTypes, Object[] paramValues) { if (clazz == null || methodName == null) { throw new MethodNotFoundException("Method not found: " + clazz + "." + methodName + "(" + paramString(paramTypes) + ")"); } if (paramTypes == null) { paramTypes = getTypesFromValues(paramValues); } Method[] methods = clazz.getMethods(); List wrappers = Wrapper.wrap(methods, methodName); Wrapper result = findWrapper(clazz, wrappers, methodName, paramTypes, paramValues); if (result == null) { return null; } return getMethod(clazz, base, (Method) result.unWrap()); } @SuppressWarnings("null") private static Wrapper findWrapper(Class clazz, List wrappers, String name, Class[] paramTypes, Object[] paramValues) { List assignableCandidates = new ArrayList<>(); List coercibleCandidates = new ArrayList<>(); List varArgsCandidates = new ArrayList<>(); int paramCount; if (paramTypes == null) { paramCount = 0; } else { paramCount = paramTypes.length; } for (Wrapper w : wrappers) { Class[] mParamTypes = w.getParameterTypes(); int mParamCount; if (mParamTypes == null) { mParamCount = 0; } else { mParamCount = mParamTypes.length; } // Check the number of parameters if (!(paramCount == mParamCount || (w.isVarArgs() && paramCount >= mParamCount - 1))) { // Method has wrong number of parameters continue; } // Check the parameters match boolean assignable = false; boolean coercible = false; boolean varArgs = false; boolean noMatch = false; for (int i = 0; i < mParamCount; i++) { if (i == (mParamCount - 1) && w.isVarArgs()) { varArgs = true; // exact var array type match if (mParamCount == paramCount) { if (mParamTypes[i] == paramTypes[i]) { continue; } } // unwrap the array's component type Class varType = mParamTypes[i].getComponentType(); for (int j = i; j < paramCount; j++) { if (!isAssignableFrom(paramTypes[j], varType) && !(paramValues != null && j < paramValues.length && isCoercibleFrom(paramValues[j], varType))) { noMatch = true; break; } } } else if (mParamTypes[i].equals(paramTypes[i])) { } else if (isAssignableFrom(paramTypes[i], mParamTypes[i])) { assignable = true; } else { if (paramValues == null || i >= paramValues.length) { noMatch = true; break; } else { if (isCoercibleFrom(paramValues[i], mParamTypes[i])) { coercible = true; } else { noMatch = true; break; } } } } if (noMatch) { continue; } if (varArgs) { varArgsCandidates.add(w); } else if (coercible) { coercibleCandidates.add(w); } else if (assignable) { assignableCandidates.add(w); } else { // If a method is found where every parameter matches exactly, // return it return w; } } String errorMsg = "Unable to find unambiguous method: " + clazz + "." + name + "(" + paramString(paramTypes) + ")"; if (!assignableCandidates.isEmpty()) { return findMostSpecificWrapper(assignableCandidates, paramTypes, false, errorMsg); } else if (!coercibleCandidates.isEmpty()) { return findMostSpecificWrapper(coercibleCandidates, paramTypes, true, errorMsg); } else if (!varArgsCandidates.isEmpty()) { return findMostSpecificWrapper(varArgsCandidates, paramTypes, true, errorMsg); } else { throw new MethodNotFoundException("Method not found: " + clazz + "." + name + "(" + paramString(paramTypes) + ")"); } } private static Wrapper findMostSpecificWrapper(List candidates, Class[] matchingTypes, boolean elSpecific, String errorMsg) { List ambiguouses = new ArrayList<>(); for (Wrapper candidate : candidates) { boolean lessSpecific = false; Iterator it = ambiguouses.iterator(); while (it.hasNext()) { int result = isMoreSpecific(candidate, it.next(), matchingTypes, elSpecific); if (result == 1) { it.remove(); } else if (result == -1) { lessSpecific = true; } } if (!lessSpecific) { ambiguouses.add(candidate); } } if (ambiguouses.size() > 1) { throw new MethodNotFoundException(errorMsg); } return ambiguouses.get(0); } private static int isMoreSpecific(Wrapper wrapper1, Wrapper wrapper2, Class[] matchingTypes, boolean elSpecific) { Class[] paramTypes1 = wrapper1.getParameterTypes(); Class[] paramTypes2 = wrapper2.getParameterTypes(); if (wrapper1.isVarArgs()) { // JLS8 15.12.2.5 Choosing the Most Specific Method int length = Math.max(Math.max(paramTypes1.length, paramTypes2.length), matchingTypes.length); paramTypes1 = getComparingParamTypesForVarArgsMethod(paramTypes1, length); paramTypes2 = getComparingParamTypesForVarArgsMethod(paramTypes2, length); if (length > matchingTypes.length) { Class[] matchingTypes2 = new Class[length]; System.arraycopy(matchingTypes, 0, matchingTypes2, 0, matchingTypes.length); matchingTypes = matchingTypes2; } } int result = 0; for (int i = 0; i < paramTypes1.length; i++) { if (paramTypes1[i] != paramTypes2[i]) { int r2 = isMoreSpecific(paramTypes1[i], paramTypes2[i], matchingTypes[i], elSpecific); if (r2 == 1) { if (result == -1) { return 0; } result = 1; } else if (r2 == -1) { if (result == 1) { return 0; } result = -1; } else { return 0; } } } if (result == 0) { // The nature of bridge methods is such that it actually // doesn't matter which one we pick as long as we pick // one. That said, pick the 'right' one (the non-bridge // one) anyway. result = Boolean.compare(wrapper1.isBridge(), wrapper2.isBridge()); } return result; } private static int isMoreSpecific(Class type1, Class type2, Class matchingType, boolean elSpecific) { type1 = getBoxingTypeIfPrimitive(type1); type2 = getBoxingTypeIfPrimitive(type2); if (type2.isAssignableFrom(type1)) { return 1; } else if (type1.isAssignableFrom(type2)) { return -1; } else { if (elSpecific) { /* * Number will be treated as more specific * * ASTInteger only return Long or BigInteger, no Byte / Short / Integer. ASTFloatingPoint also. * */ if (matchingType != null && Number.class.isAssignableFrom(matchingType)) { boolean b1 = Number.class.isAssignableFrom(type1) || type1.isPrimitive(); boolean b2 = Number.class.isAssignableFrom(type2) || type2.isPrimitive(); if (b1 && !b2) { return 1; } else if (b2 && !b1) { return -1; } else { return 0; } } return 0; } else { return 0; } } } private static Class getBoxingTypeIfPrimitive(Class clazz) { if (clazz.isPrimitive()) { if (clazz == Boolean.TYPE) { return Boolean.class; } if (clazz == Character.TYPE) { return Character.class; } if (clazz == Byte.TYPE) { return Byte.class; } if (clazz == Short.TYPE) { return Short.class; } if (clazz == Integer.TYPE) { return Integer.class; } if (clazz == Long.TYPE) { return Long.class; } if (clazz == Float.TYPE) { return Float.class; } return Double.class; } else { return clazz; } } private static Class[] getComparingParamTypesForVarArgsMethod(Class[] paramTypes, int length) { Class[] result = new Class[length]; System.arraycopy(paramTypes, 0, result, 0, paramTypes.length - 1); Class type = paramTypes[paramTypes.length - 1].getComponentType(); for (int i = paramTypes.length - 1; i < length; i++) { result[i] = type; } return result; } private static final String paramString(Class[] types) { if (types != null) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < types.length; i++) { if (types[i] == null) { sb.append("null, "); } else { sb.append(types[i].getName()).append(", "); } } if (sb.length() > 2) { sb.setLength(sb.length() - 2); } return sb.toString(); } return null; } static boolean isAssignableFrom(Class src, Class target) { // src will always be an object // Short-cut. null is always assignable to an object and in Jakarta Expression Language null // can always be coerced to a valid value for a primitive if (src == null) { return true; } target = getBoxingTypeIfPrimitive(target); return target.isAssignableFrom(src); } private static boolean isCoercibleFrom(Object src, Class target) { // TODO: This isn't pretty but it works. Significant refactoring would // be required to avoid the exception. try { ELManager.getExpressionFactory().coerceToType(src, target); } catch (Exception e) { return false; } return true; } private static Class[] getTypesFromValues(Object[] values) { if (values == null) { return null; } Class result[] = new Class[values.length]; for (int i = 0; i < values.length; i++) { if (values[i] == null) { result[i] = null; } else { result[i] = values[i].getClass(); } } return result; } /* * Get a public method form a public class or interface of a given method. Note that if a PropertyDescriptor is obtained * for a non-public class that implements a public interface, the read/write methods will be for the class, and * therefore inaccessible. To correct this, a version of the same method must be found in a superclass or interface. */ static Method getMethod(Class type, Object base, Method m) { // If base is null, method MUST be static // If base is non-null, method may be static or non-static if (m == null || (Modifier.isPublic(type.getModifiers()) && (canAccess(base, m) || base != null && canAccess(null, m)))) { return m; } Class[] inf = type.getInterfaces(); Method mp = null; for (int i = 0; i < inf.length; i++) { try { mp = inf[i].getMethod(m.getName(), m.getParameterTypes()); mp = getMethod(mp.getDeclaringClass(), base, mp); if (mp != null) { return mp; } } catch (NoSuchMethodException e) { // Ignore } } Class sup = type.getSuperclass(); if (sup != null) { try { mp = sup.getMethod(m.getName(), m.getParameterTypes()); mp = getMethod(mp.getDeclaringClass(), base, mp); if (mp != null) { return mp; } } catch (NoSuchMethodException e) { // Ignore } } return null; } static Constructor getConstructor(Class type, Constructor c) { if (c == null || Modifier.isPublic(type.getModifiers())) { return c; } Constructor cp = null; Class sup = type.getSuperclass(); if (sup != null) { try { cp = sup.getConstructor(c.getParameterTypes()); cp = getConstructor(cp.getDeclaringClass(), cp); if (cp != null) { return cp; } } catch (NoSuchMethodException e) { // Ignore } } return null; } static boolean canAccess(Object base, AccessibleObject accessibleObject) { try { return accessibleObject.canAccess(base); } catch (IllegalArgumentException iae) { return false; } } @SuppressWarnings("null") // params cannot be null when used static Object[] buildParameters(ELContext context, Class[] parameterTypes, boolean isVarArgs, Object[] params) { Object[] parameters = null; if (parameterTypes.length > 0) { parameters = new Object[parameterTypes.length]; int paramCount = params == null ? 0 : params.length; if (isVarArgs) { int varArgIndex = parameterTypes.length - 1; // First argCount-1 parameters are standard for (int i = 0; (i < varArgIndex && i < paramCount); i++) { parameters[i] = context.convertToType(params[i], parameterTypes[i]); } // Last parameter is the varargs if (parameterTypes.length == paramCount && params[varArgIndex] != null && parameterTypes[varArgIndex] == params[varArgIndex].getClass()) { parameters[varArgIndex] = params[varArgIndex]; } else { Class varArgClass = parameterTypes[varArgIndex].getComponentType(); final Object varargs = Array.newInstance(varArgClass, (paramCount - varArgIndex)); for (int i = (varArgIndex); i < paramCount; i++) { Array.set(varargs, i - varArgIndex, context.convertToType(params[i], varArgClass)); } parameters[varArgIndex] = varargs; } } else { for (int i = 0; i < parameterTypes.length && i < paramCount; i++) { parameters[i] = context.convertToType(params[i], parameterTypes[i]); } } } return parameters; } private abstract static class Wrapper { public static List wrap(Method[] methods, String name) { List result = new ArrayList<>(); for (Method method : methods) { if (method.getName().equals(name)) { result.add(new MethodWrapper(method)); } } return result; } public static List wrap(Constructor[] constructors) { List result = new ArrayList<>(); for (Constructor constructor : constructors) { result.add(new ConstructorWrapper(constructor)); } return result; } public abstract Object unWrap(); public abstract Class[] getParameterTypes(); public abstract boolean isVarArgs(); public abstract boolean isBridge(); } private static class MethodWrapper extends Wrapper { private final Method m; public MethodWrapper(Method m) { this.m = m; } @Override public Object unWrap() { return m; } @Override public Class[] getParameterTypes() { return m.getParameterTypes(); } @Override public boolean isVarArgs() { return m.isVarArgs(); } @Override public boolean isBridge() { return m.isBridge(); } } private static class ConstructorWrapper extends Wrapper { private final Constructor c; public ConstructorWrapper(Constructor c) { this.c = c; } @Override public Object unWrap() { return c; } @Override public Class[] getParameterTypes() { return c.getParameterTypes(); } @Override public boolean isVarArgs() { return c.isVarArgs(); } @Override public boolean isBridge() { return false; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy