| /* |
| * 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 freemarker.ext.beans; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.math.BigDecimal; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import freemarker.core.BugException; |
| import freemarker.template.TemplateModel; |
| import freemarker.template.TemplateModelException; |
| import freemarker.template.Version; |
| import freemarker.template.utility.ClassUtil; |
| |
| /** |
| * The argument types of a method call; usable as cache key. |
| */ |
| final class ArgumentTypes { |
| |
| /** |
| * Conversion difficulty: Lowest; Java Reflection will do it automatically. |
| */ |
| private static final int CONVERSION_DIFFICULTY_REFLECTION = 0; |
| |
| /** |
| * Conversion difficulty: Java reflection API will won't convert it, FreeMarker has to do it. |
| */ |
| private static final int CONVERSION_DIFFICULTY_FREEMARKER = 1; |
| |
| /** |
| * Conversion difficulty: Highest; conversion is not possible. |
| */ |
| private static final int CONVERSION_DIFFICULTY_IMPOSSIBLE = 2; |
| |
| /** |
| * The types of the arguments; for varags this contains the exploded list (not the array). |
| */ |
| private final Class<?>[] types; |
| |
| private final boolean bugfixed; |
| |
| /** |
| * @param args The actual arguments. A varargs argument should be present exploded, no as an array. |
| * @param bugfixed Introduced in 2.3.21, sets this object to a mode that works well with {@link BeansWrapper}-s |
| * created with {@link Version} 2.3.21 or higher. |
| */ |
| ArgumentTypes(Object[] args, boolean bugfixed) { |
| int ln = args.length; |
| Class<?>[] typesTmp = new Class[ln]; |
| for (int i = 0; i < ln; ++i) { |
| Object arg = args[i]; |
| typesTmp[i] = arg == null |
| ? (bugfixed ? Null.class : Object.class) |
| : arg.getClass(); |
| } |
| |
| // `typesTmp` is used so the array is only modified before it's stored in the final `types` field (see JSR-133) |
| types = typesTmp; |
| this.bugfixed = bugfixed; |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = 0; |
| for (int i = 0; i < types.length; ++i) { |
| hash ^= types[i].hashCode(); |
| } |
| return hash; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o instanceof ArgumentTypes) { |
| ArgumentTypes cs = (ArgumentTypes) o; |
| if (cs.types.length != types.length) { |
| return false; |
| } |
| for (int i = 0; i < types.length; ++i) { |
| if (cs.types[i] != types[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * @return Possibly {@link EmptyCallableMemberDescriptor#NO_SUCH_METHOD} or |
| * {@link EmptyCallableMemberDescriptor#AMBIGUOUS_METHOD}. |
| */ |
| MaybeEmptyCallableMemberDescriptor getMostSpecific( |
| List<ReflectionCallableMemberDescriptor> memberDescs, boolean varArg) { |
| LinkedList<CallableMemberDescriptor> applicables = getApplicables(memberDescs, varArg); |
| if (applicables.isEmpty()) { |
| return EmptyCallableMemberDescriptor.NO_SUCH_METHOD; |
| } |
| if (applicables.size() == 1) { |
| return applicables.getFirst(); |
| } |
| |
| LinkedList<CallableMemberDescriptor> maximals = new LinkedList<>(); |
| for (CallableMemberDescriptor applicable : applicables) { |
| boolean lessSpecific = false; |
| for (Iterator<CallableMemberDescriptor> maximalsIter = maximals.iterator(); |
| maximalsIter.hasNext(); ) { |
| CallableMemberDescriptor maximal = maximalsIter.next(); |
| final int cmpRes = compareParameterListPreferability( |
| applicable.getParamTypes(), maximal.getParamTypes(), varArg); |
| if (cmpRes > 0) { |
| maximalsIter.remove(); |
| } else if (cmpRes < 0) { |
| lessSpecific = true; |
| } |
| } |
| if (!lessSpecific) { |
| maximals.addLast(applicable); |
| } |
| } |
| if (maximals.size() > 1) { |
| return EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD; |
| } |
| return maximals.getFirst(); |
| } |
| |
| /** |
| * Tells if among the parameter list of two methods, which one fits this argument list better. |
| * This method assumes that the parameter lists are applicable to this argument lists; if that's not ensured, |
| * what the result will be is undefined. |
| * |
| * <p>This method behaves differently in {@code bugfixed}-mode (used when a {@link BeansWrapper} is created with |
| * incompatible improvements set to 2.3.21 or higher). Below we describe the bugfixed behavior only. |
| * |
| * <p>The decision is made by comparing the preferability of each parameter types of the same position in a loop. |
| * At the end, the parameter list with the more preferred parameters will be the preferred one. If both parameter |
| * lists has the same amount of preferred parameters, the one that has the first (lower index) preferred parameter |
| * is the preferred one. Otherwise the two parameter list are considered to be equal in terms of preferability. |
| * |
| * <p>If there's no numerical conversion involved, the preferability of two parameter types is decided on how |
| * specific their types are. For example, {@code String} is more specific than {@link Object} (because |
| * {@code Object.class.isAssignableFrom(String.class)}-s), and so {@code String} is preferred. Primitive |
| * types are considered to be more specific than the corresponding boxing class (like {@code boolean} is more |
| * specific than {@code Boolean}, because the former can't store {@code null}). The preferability decision gets |
| * trickier when there's a possibility of numerical conversion from the actual argument type to the type of some of |
| * the parameters. If such conversion is only possible for one of the competing parameter types, that parameter |
| * automatically wins. If it's possible for both, {@link OverloadedNumberUtil#getArgumentConversionPrice} will |
| * be used to calculate the conversion "price", and the parameter type with lowest price wins. There are also |
| * a twist with array-to-list and list-to-array conversions; we try to avoid those, so the parameter where such |
| * conversion isn't needed will always win. |
| * |
| * @param paramTypes1 The parameter types of one of the competing methods |
| * @param paramTypes2 The parameter types of the other competing method |
| * @param varArg Whether these competing methods are varargs methods. |
| * @return More than 0 if the first parameter list is preferred, less then 0 if the other is preferred, |
| * 0 if there's no decision |
| */ |
| int compareParameterListPreferability(Class<?>[] paramTypes1, Class<?>[] paramTypes2, boolean varArg) { |
| final int argTypesLen = types.length; |
| final int paramTypes1Len = paramTypes1.length; |
| final int paramTypes2Len = paramTypes2.length; |
| //assert varArg || paramTypes1Len == paramTypes2Len; |
| |
| if (bugfixed) { |
| int paramList1WeakWinCnt = 0; |
| int paramList2WeakWinCnt = 0; |
| int paramList1WinCnt = 0; |
| int paramList2WinCnt = 0; |
| int paramList1StrongWinCnt = 0; |
| int paramList2StrongWinCnt = 0; |
| int paramList1VeryStrongWinCnt = 0; |
| int paramList2VeryStrongWinCnt = 0; |
| int firstWinerParamList = 0; |
| for (int i = 0; i < argTypesLen; i++) { |
| final Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, i, varArg); |
| final Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, i, varArg); |
| |
| final int winerParam; // 1 => paramType1; -1 => paramType2; 0 => draw |
| if (paramType1 == paramType2) { |
| winerParam = 0; |
| } else { |
| final Class<?> argType = types[i]; |
| final boolean argIsNum = Number.class.isAssignableFrom(argType); |
| |
| final int numConvPrice1; |
| if (argIsNum && ClassUtil.isNumerical(paramType1)) { |
| final Class<?> nonPrimParamType1 = paramType1.isPrimitive() |
| ? ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1; |
| numConvPrice1 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType1); |
| } else { |
| numConvPrice1 = Integer.MAX_VALUE; |
| } |
| // numConvPrice1 is Integer.MAX_VALUE if either: |
| // - argType and paramType1 aren't both numerical |
| // - FM doesn't know some of the numerical types, or the conversion between them is not allowed |
| |
| final int numConvPrice2; |
| if (argIsNum && ClassUtil.isNumerical(paramType2)) { |
| final Class<?> nonPrimParamType2 = paramType2.isPrimitive() |
| ? ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2; |
| numConvPrice2 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType2); |
| } else { |
| numConvPrice2 = Integer.MAX_VALUE; |
| } |
| |
| if (numConvPrice1 == Integer.MAX_VALUE) { |
| if (numConvPrice2 == Integer.MAX_VALUE) { // No numerical conversions anywhere |
| // List to array conversions (unwrapping sometimes makes a List instead of an array) |
| if (List.class.isAssignableFrom(argType) |
| && (paramType1.isArray() || paramType2.isArray())) { |
| if (paramType1.isArray()) { |
| if (paramType2.isArray()) { // both paramType1 and paramType2 are arrays |
| int r = compareParameterListPreferability_cmpTypeSpecificty( |
| paramType1.getComponentType(), paramType2.getComponentType()); |
| // Because we don't know if the List items are instances of the component |
| // type or not, we prefer the safer choice, which is the more generic array: |
| if (r > 0) { |
| winerParam = 2; |
| paramList2StrongWinCnt++; |
| } else if (r < 0) { |
| winerParam = 1; |
| paramList1StrongWinCnt++; |
| } else { |
| winerParam = 0; |
| } |
| } else { // paramType1 is array, paramType2 isn't |
| // Avoid List to array conversion if the other way makes any sense: |
| if (Collection.class.isAssignableFrom(paramType2)) { |
| winerParam = 2; |
| paramList2StrongWinCnt++; |
| } else { |
| winerParam = 1; |
| paramList1WeakWinCnt++; |
| } |
| } |
| } else { // paramType2 is array, paramType1 isn't |
| // Avoid List to array conversion if the other way makes any sense: |
| if (Collection.class.isAssignableFrom(paramType1)) { |
| winerParam = 1; |
| paramList1StrongWinCnt++; |
| } else { |
| winerParam = 2; |
| paramList2WeakWinCnt++; |
| } |
| } |
| } else if (argType.isArray() |
| && (List.class.isAssignableFrom(paramType1) |
| || List.class.isAssignableFrom(paramType2))) { |
| // Array to List conversions (unwrapping sometimes makes an array instead of a List) |
| if (List.class.isAssignableFrom(paramType1)) { |
| if (List.class.isAssignableFrom(paramType2)) { |
| // Both paramType1 and paramType2 extends List |
| winerParam = 0; |
| } else { |
| // Only paramType1 extends List |
| winerParam = 2; |
| paramList2VeryStrongWinCnt++; |
| } |
| } else { |
| // Only paramType2 extends List |
| winerParam = 1; |
| paramList1VeryStrongWinCnt++; |
| } |
| } else { // No list to/from array conversion |
| final int r = compareParameterListPreferability_cmpTypeSpecificty( |
| paramType1, paramType2); |
| if (r > 0) { |
| winerParam = 1; |
| if (r > 1) { |
| paramList1WinCnt++; |
| } else { |
| paramList1WeakWinCnt++; |
| } |
| } else if (r < 0) { |
| winerParam = -1; |
| if (r < -1) { |
| paramList2WinCnt++; |
| } else { |
| paramList2WeakWinCnt++; |
| } |
| } else { |
| winerParam = 0; |
| } |
| } |
| } else { // No num. conv. of param1, num. conv. of param2 |
| winerParam = -1; |
| paramList2WinCnt++; |
| } |
| } else if (numConvPrice2 == Integer.MAX_VALUE) { // Num. conv. of param1, not of param2 |
| winerParam = 1; |
| paramList1WinCnt++; |
| } else { // Num. conv. of both param1 and param2 |
| if (numConvPrice1 != numConvPrice2) { |
| if (numConvPrice1 < numConvPrice2) { |
| winerParam = 1; |
| if (numConvPrice1 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE |
| && numConvPrice2 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) { |
| paramList1StrongWinCnt++; |
| } else { |
| paramList1WinCnt++; |
| } |
| } else { |
| winerParam = -1; |
| if (numConvPrice2 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE |
| && numConvPrice1 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) { |
| paramList2StrongWinCnt++; |
| } else { |
| paramList2WinCnt++; |
| } |
| } |
| } else { |
| winerParam = (paramType1.isPrimitive() ? 1 : 0) - (paramType2.isPrimitive() ? 1 : 0); |
| if (winerParam == 1) paramList1WeakWinCnt++; |
| else if (winerParam == -1) paramList2WeakWinCnt++; |
| } |
| } |
| } // when paramType1 != paramType2 |
| |
| if (firstWinerParamList == 0 && winerParam != 0) { |
| firstWinerParamList = winerParam; |
| } |
| } // for each parameter types |
| |
| if (paramList1VeryStrongWinCnt != paramList2VeryStrongWinCnt) { |
| return paramList1VeryStrongWinCnt - paramList2VeryStrongWinCnt; |
| } else if (paramList1StrongWinCnt != paramList2StrongWinCnt) { |
| return paramList1StrongWinCnt - paramList2StrongWinCnt; |
| } else if (paramList1WinCnt != paramList2WinCnt) { |
| return paramList1WinCnt - paramList2WinCnt; |
| } else if (paramList1WeakWinCnt != paramList2WeakWinCnt) { |
| return paramList1WeakWinCnt - paramList2WeakWinCnt; |
| } else if (firstWinerParamList != 0) { // paramList1WinCnt == paramList2WinCnt |
| return firstWinerParamList; |
| } else { // still undecided |
| if (varArg) { |
| if (paramTypes1Len == paramTypes2Len) { |
| // If we had a 0-length varargs array in both methods, we also compare the types at the |
| // index of the varargs parameter, like if we had a single varargs argument. However, this |
| // time we don't have an argument type, so we can only decide based on type specificity: |
| if (argTypesLen == paramTypes1Len - 1) { |
| Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, argTypesLen, true); |
| Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, argTypesLen, true); |
| if (ClassUtil.isNumerical(paramType1) && ClassUtil.isNumerical(paramType2)) { |
| int r = OverloadedNumberUtil.compareNumberTypeSpecificity(paramType1, paramType2); |
| if (r != 0) return r; |
| // falls through |
| } |
| return compareParameterListPreferability_cmpTypeSpecificty(paramType1, paramType2); |
| } else { |
| return 0; |
| } |
| } else { |
| // The method with more fixed parameters wins: |
| return paramTypes1Len - paramTypes2Len; |
| } |
| } else { |
| return 0; |
| } |
| } |
| } else { // non-bugfixed (backward-compatible) mode |
| boolean paramTypes1HasAMoreSpecific = false; |
| boolean paramTypes2HasAMoreSpecific = false; |
| for (int i = 0; i < paramTypes1Len; ++i) { |
| Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, i, varArg); |
| Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, i, varArg); |
| if (paramType1 != paramType2) { |
| paramTypes1HasAMoreSpecific = |
| paramTypes1HasAMoreSpecific |
| || _MethodUtil.isMoreOrSameSpecificParameterType(paramType1, paramType2, false, 0) != 0; |
| paramTypes2HasAMoreSpecific = |
| paramTypes2HasAMoreSpecific |
| || _MethodUtil.isMoreOrSameSpecificParameterType(paramType2, paramType1, false, 0) != 0; |
| } |
| } |
| |
| if (paramTypes1HasAMoreSpecific) { |
| return paramTypes2HasAMoreSpecific ? 0 : 1; |
| } else if (paramTypes2HasAMoreSpecific) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| } |
| |
| /** |
| * Trivial comparison of type specificities; unaware of numerical conversions. |
| * |
| * @return Less-than-0, 0, or more-than-0 depending on which side is more specific. The absolute value is 1 if |
| * the difference is only in primitive VS non-primitive, more otherwise. |
| */ |
| private int compareParameterListPreferability_cmpTypeSpecificty( |
| final Class<?> paramType1, final Class<?> paramType2) { |
| // The more specific (smaller) type wins. |
| |
| final Class<?> nonPrimParamType1 = paramType1.isPrimitive() |
| ? ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1; |
| final Class<?> nonPrimParamType2 = paramType2.isPrimitive() |
| ? ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2; |
| |
| if (nonPrimParamType1 == nonPrimParamType2) { |
| if (nonPrimParamType1 != paramType1) { |
| if (nonPrimParamType2 != paramType2) { |
| return 0; // identical prim. types; shouldn't ever be reached |
| } else { |
| return 1; // param1 is prim., param2 is non prim. |
| } |
| } else if (nonPrimParamType2 != paramType2) { |
| return -1; // param1 is non-prim., param2 is prim. |
| } else { |
| return 0; // identical non-prim. types |
| } |
| } else if (nonPrimParamType2.isAssignableFrom(nonPrimParamType1)) { |
| return 2; |
| } else if (nonPrimParamType1.isAssignableFrom(nonPrimParamType2)) { |
| return -2; |
| } if (nonPrimParamType1 == Character.class && nonPrimParamType2.isAssignableFrom(String.class)) { |
| return 2; // A character is a 1 long string in FTL, so we pretend that it's a String subtype. |
| } if (nonPrimParamType2 == Character.class && nonPrimParamType1.isAssignableFrom(String.class)) { |
| return -2; |
| } else { |
| return 0; // unrelated types |
| } |
| } |
| |
| private static Class<?> getParamType(Class<?>[] paramTypes, int paramTypesLen, int i, boolean varArg) { |
| return varArg && i >= paramTypesLen - 1 |
| ? paramTypes[paramTypesLen - 1].getComponentType() |
| : paramTypes[i]; |
| } |
| |
| /** |
| * Returns all methods that are applicable to actual |
| * parameter types represented by this ArgumentTypes object. |
| */ |
| LinkedList<CallableMemberDescriptor> getApplicables( |
| List<ReflectionCallableMemberDescriptor> memberDescs, boolean varArg) { |
| LinkedList<CallableMemberDescriptor> applicables = new LinkedList<>(); |
| for (ReflectionCallableMemberDescriptor memberDesc : memberDescs) { |
| int difficulty = isApplicable(memberDesc, varArg); |
| if (difficulty != CONVERSION_DIFFICULTY_IMPOSSIBLE) { |
| if (difficulty == CONVERSION_DIFFICULTY_REFLECTION) { |
| applicables.add(memberDesc); |
| } else if (difficulty == CONVERSION_DIFFICULTY_FREEMARKER) { |
| applicables.add(new SpecialConversionCallableMemberDescriptor(memberDesc)); |
| } else { |
| throw new BugException(); |
| } |
| } |
| } |
| return applicables; |
| } |
| |
| /** |
| * Returns if the supplied method is applicable to actual |
| * parameter types represented by this ArgumentTypes object, also tells |
| * how difficult that conversion is. |
| * |
| * @return One of the {@code CONVERSION_DIFFICULTY_...} constants. |
| */ |
| private int isApplicable(ReflectionCallableMemberDescriptor memberDesc, boolean varArg) { |
| final Class<?>[] paramTypes = memberDesc.getParamTypes(); |
| final int cl = types.length; |
| final int fl = paramTypes.length - (varArg ? 1 : 0); |
| if (varArg) { |
| if (cl < fl) { |
| return CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } |
| } else { |
| if (cl != fl) { |
| return CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } |
| } |
| |
| int maxDifficulty = 0; |
| for (int i = 0; i < fl; ++i) { |
| int difficulty = isMethodInvocationConvertible(paramTypes[i], types[i]); |
| if (difficulty == CONVERSION_DIFFICULTY_IMPOSSIBLE) { |
| return CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } |
| if (maxDifficulty < difficulty) { |
| maxDifficulty = difficulty; |
| } |
| } |
| if (varArg) { |
| Class<?> varArgParamType = paramTypes[fl].getComponentType(); |
| for (int i = fl; i < cl; ++i) { |
| int difficulty = isMethodInvocationConvertible(varArgParamType, types[i]); |
| if (difficulty == CONVERSION_DIFFICULTY_IMPOSSIBLE) { |
| return CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } |
| if (maxDifficulty < difficulty) { |
| maxDifficulty = difficulty; |
| } |
| } |
| } |
| return maxDifficulty; |
| } |
| |
| /** |
| * Determines whether a type is convertible to another type via |
| * method invocation conversion, and if so, what kind of conversion is needed. |
| * It treates the object type counterpart of primitive types as if they were the primitive types |
| * (that is, a Boolean actual parameter type matches boolean primitive formal type). This behavior |
| * is because this method is used to determine applicable methods for |
| * an actual parameter list, and primitive types are represented by |
| * their object duals in reflective method calls. |
| * @param formal the parameter type to which the actual |
| * parameter type should be convertible; possibly a primitive type |
| * @param actual the argument type; not a primitive type, maybe {@link Null}. |
| * |
| * @return One of the {@code CONVERSION_DIFFICULTY_...} constants. |
| */ |
| private int isMethodInvocationConvertible(final Class<?> formal, final Class<?> actual) { |
| // Check for identity or widening reference conversion |
| if (formal.isAssignableFrom(actual) && actual != CharacterOrString.class) { |
| return CONVERSION_DIFFICULTY_REFLECTION; |
| } else if (bugfixed) { |
| final Class<?> formalNP; |
| if (formal.isPrimitive()) { |
| if (actual == Null.class) { |
| return CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } |
| |
| formalNP = ClassUtil.primitiveClassToBoxingClass(formal); |
| if (actual == formalNP) { |
| // Character and char, etc. |
| return CONVERSION_DIFFICULTY_REFLECTION; |
| } |
| } else { // formal is non-primitive |
| if (actual == Null.class) { |
| return CONVERSION_DIFFICULTY_REFLECTION; |
| } |
| |
| formalNP = formal; |
| } |
| if (Number.class.isAssignableFrom(actual) && Number.class.isAssignableFrom(formalNP)) { |
| return OverloadedNumberUtil.getArgumentConversionPrice(actual, formalNP) == Integer.MAX_VALUE |
| ? CONVERSION_DIFFICULTY_IMPOSSIBLE : CONVERSION_DIFFICULTY_REFLECTION; |
| } else if (formal.isArray()) { |
| // BeansWrapper method/constructor calls convert from List to array automatically |
| return List.class.isAssignableFrom(actual) |
| ? CONVERSION_DIFFICULTY_FREEMARKER : CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } else if (actual.isArray() && formal.isAssignableFrom(List.class)) { |
| // BeansWrapper method/constructor calls convert from array to List automatically |
| return CONVERSION_DIFFICULTY_FREEMARKER; |
| } else if (actual == CharacterOrString.class |
| && (formal.isAssignableFrom(String.class) |
| || formal.isAssignableFrom(Character.class) || formal == char.class)) { |
| return CONVERSION_DIFFICULTY_FREEMARKER; |
| } else { |
| return CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } |
| } else { // if !bugfixed |
| // This non-bugfixed (backward-compatible, pre-2.3.21) branch: |
| // - Doesn't convert *to* non-primitive numerical types (unless the argument is a BigDecimal). |
| // (This is like in Java language, which also doesn't coerce to non-primitive numerical types.) |
| // - Doesn't support BigInteger conversions |
| // - Doesn't support NumberWithFallbackType-s and CharacterOrString-s. Those are only produced in bugfixed |
| // mode anyway. |
| // - Doesn't support conversion between array and List |
| if (formal.isPrimitive()) { |
| // Check for boxing with widening primitive conversion. Note that |
| // actual parameters are never primitives. |
| // It doesn't do the same with boxing types... that was a bug. |
| if (formal == Boolean.TYPE) { |
| return actual == Boolean.class |
| ? CONVERSION_DIFFICULTY_REFLECTION : CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } else if (formal == Double.TYPE && |
| (actual == Double.class || actual == Float.class || |
| actual == Long.class || actual == Integer.class || |
| actual == Short.class || actual == Byte.class)) { |
| return CONVERSION_DIFFICULTY_REFLECTION; |
| } else if (formal == Integer.TYPE && |
| (actual == Integer.class || actual == Short.class || |
| actual == Byte.class)) { |
| return CONVERSION_DIFFICULTY_REFLECTION; |
| } else if (formal == Long.TYPE && |
| (actual == Long.class || actual == Integer.class || |
| actual == Short.class || actual == Byte.class)) { |
| return CONVERSION_DIFFICULTY_REFLECTION; |
| } else if (formal == Float.TYPE && |
| (actual == Float.class || actual == Long.class || |
| actual == Integer.class || actual == Short.class || |
| actual == Byte.class)) { |
| return CONVERSION_DIFFICULTY_REFLECTION; |
| } else if (formal == Character.TYPE) { |
| return actual == Character.class |
| ? CONVERSION_DIFFICULTY_REFLECTION : CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } else if (formal == Byte.TYPE && actual == Byte.class) { |
| return CONVERSION_DIFFICULTY_REFLECTION; |
| } else if (formal == Short.TYPE && |
| (actual == Short.class || actual == Byte.class)) { |
| return CONVERSION_DIFFICULTY_REFLECTION; |
| } else if (BigDecimal.class.isAssignableFrom(actual) && ClassUtil.isNumerical(formal)) { |
| // Special case for BigDecimals as we deem BigDecimal to be |
| // convertible to any numeric type - either object or primitive. |
| // This can actually cause us trouble as this is a narrowing |
| // conversion, not widening. |
| return CONVERSION_DIFFICULTY_REFLECTION; |
| } else { |
| return CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } |
| } else { |
| return CONVERSION_DIFFICULTY_IMPOSSIBLE; |
| } |
| } |
| } |
| |
| /** |
| * Symbolizes the class of null (it's missing from Java). |
| */ |
| private static class Null { |
| |
| // Can't be instantiated |
| private Null() { } |
| |
| } |
| |
| /** |
| * Used instead of {@link ReflectionCallableMemberDescriptor} when the method is only applicable |
| * ({@link #isApplicable}) with conversion that Java reflection won't do. It delegates to a |
| * {@link ReflectionCallableMemberDescriptor}, but it adds the necessary conversions to the invocation methods. |
| */ |
| private static final class SpecialConversionCallableMemberDescriptor extends CallableMemberDescriptor { |
| |
| private final ReflectionCallableMemberDescriptor callableMemberDesc; |
| |
| SpecialConversionCallableMemberDescriptor(ReflectionCallableMemberDescriptor callableMemberDesc) { |
| this.callableMemberDesc = callableMemberDesc; |
| } |
| |
| @Override |
| TemplateModel invokeMethod(BeansWrapper bw, Object obj, Object[] args) throws TemplateModelException, |
| InvocationTargetException, IllegalAccessException { |
| convertArgsToReflectionCompatible(bw, args); |
| return callableMemberDesc.invokeMethod(bw, obj, args); |
| } |
| |
| @Override |
| Object invokeConstructor(BeansWrapper bw, Object[] args) throws IllegalArgumentException, |
| InstantiationException, IllegalAccessException, InvocationTargetException, TemplateModelException { |
| convertArgsToReflectionCompatible(bw, args); |
| return callableMemberDesc.invokeConstructor(bw, args); |
| } |
| |
| @Override |
| String getDeclaration() { |
| return callableMemberDesc.getDeclaration(); |
| } |
| |
| @Override |
| boolean isConstructor() { |
| return callableMemberDesc.isConstructor(); |
| } |
| |
| @Override |
| boolean isStatic() { |
| return callableMemberDesc.isStatic(); |
| } |
| |
| @Override |
| boolean isVarargs() { |
| return callableMemberDesc.isVarargs(); |
| } |
| |
| @Override |
| Class<?>[] getParamTypes() { |
| return callableMemberDesc.getParamTypes(); |
| } |
| |
| @Override |
| String getName() { |
| return callableMemberDesc.getName(); |
| } |
| |
| private void convertArgsToReflectionCompatible(BeansWrapper bw, Object[] args) throws TemplateModelException { |
| Class<?>[] paramTypes = callableMemberDesc.getParamTypes(); |
| int ln = paramTypes.length; |
| for (int i = 0; i < ln; i++) { |
| Class<?> paramType = paramTypes[i]; |
| final Object arg = args[i]; |
| if (arg == null) continue; |
| |
| // Handle conversion between List and array types, in both directions. Java reflection won't do such |
| // conversion, so we have to. |
| // Most reflection-incompatible conversions were already addressed by the unwrapping. The reason |
| // this one isn't is that for overloaded methods the hint of a given parameter position is often vague, |
| // so we may end up with a List even if some parameter types at that position are arrays (remember, we |
| // have to chose one unwrapping target type, despite that we have many possible overloaded methods), or |
| // the other way around (that happens when AdapterTemplateMoldel returns an array). |
| // Later, the overloaded method selection will assume that a List argument is applicable to an array |
| // parameter, and that an array argument is applicable to a List parameter, so we end up with this |
| // situation. |
| if (paramType.isArray() && arg instanceof List) { |
| args[i] = bw.listToArray((List<?>) arg, paramType, null); |
| } |
| if (arg.getClass().isArray() && paramType.isAssignableFrom(List.class)) { |
| args[i] = bw.arrayToList(arg); |
| } |
| |
| // Handle the conversion from CharacterOrString to Character or String: |
| if (arg instanceof CharacterOrString) { |
| if (paramType == Character.class || paramType == char.class |
| || (!paramType.isAssignableFrom(String.class) |
| && paramType.isAssignableFrom(Character.class))) { |
| args[i] = Character.valueOf(((CharacterOrString) arg).getAsChar()); |
| } else { |
| args[i] = ((CharacterOrString) arg).getAsString(); |
| } |
| } |
| } |
| } |
| |
| } |
| |
| } |