/*
 * 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.
/** * The argument types of a method call; usable as cache key. */
final class ArgumentTypes {
Conversion difficulty: Lowest; Java Reflection will do it automatically.
/** * 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.
/** * 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.
/** * 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).
/** * The types of the arguments; for varags this contains the exploded list (not the array). */
private final Class<?>[] types; private final boolean bugfixed;
Params:
  • args – The actual arguments. A varargs argument should be present exploded, no as an array.
  • bugfixed – Introduced in 2.3.21, sets this object to a mode that works well with BeansWrapper-s created with Version 2.3.21 or higher.
/** * @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; }
Returns:Possibly EmptyCallableMemberDescriptor.NO_SUCH_METHOD or EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD.
/** * @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<CallableMemberDescriptor>(); 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.

This method behaves differently in bugfixed-mode (used when a BeansWrapper is created with incompatible improvements set to 2.3.21 or higher). Below we describe the bugfixed behavior only.

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.

If there's no numerical conversion involved, the preferability of two parameter types is decided on how specific their types are. For example, String is more specific than Object (because Object.class.isAssignableFrom(String.class)-s), and so String is preferred. Primitive types are considered to be more specific than the corresponding boxing class (like boolean is more specific than Boolean, because the former can't store 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, 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.

Params:
  • paramTypes1 – The parameter types of one of the competing methods
  • paramTypes2 – The parameter types of the other competing method
  • varArg – Whether these competing methods are varargs methods.
Returns:More than 0 if the first parameter list is preferred, less then 0 if the other is preferred, 0 if there's no decision
/** * 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.
Returns: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.
/** * 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.
/** * 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<CallableMemberDescriptor>(); 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.
Returns:One of the CONVERSION_DIFFICULTY_... constants.
/** * 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 <tt>CONVERSION_DIFFICULTY_...</tt> 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.
Params:
  • formal – the parameter type to which the actual parameter type should be convertible; possibly a primitive type
  • actual – the argument type; not a primitive type, maybe Null.
Returns:One of the CONVERSION_DIFFICULTY_... constants.
/** * 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 <tt>CONVERSION_DIFFICULTY_...</tt> 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).
/** * Symbolizes the class of null (it's missing from Java). */
private static class Null { // Can't be instantiated private Null() { } }
Used instead of ReflectionCallableMemberDescriptor when the method is only applicable (isApplicable) with conversion that Java reflection won't do. It delegates to a ReflectionCallableMemberDescriptor, but it adds the necessary conversions to the invocation methods.
/** * 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(); } } } } } }