http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/freemarker/ext/beans/ArgumentTypes.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/ext/beans/ArgumentTypes.java b/src/main/java/freemarker/ext/beans/ArgumentTypes.java deleted file mode 100644 index e383259..0000000 --- a/src/main/java/freemarker/ext/beans/ArgumentTypes.java +++ /dev/null @@ -1,735 +0,0 @@ -/* - * 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<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. - * - * <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<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. - * - * @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. - * @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). - */ - 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(); - } - } - } - } - - } - -} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/freemarker/ext/beans/ArrayModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/ext/beans/ArrayModel.java b/src/main/java/freemarker/ext/beans/ArrayModel.java deleted file mode 100644 index 5884c9a..0000000 --- a/src/main/java/freemarker/ext/beans/ArrayModel.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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.Array; - -import freemarker.ext.util.ModelFactory; -import freemarker.template.ObjectWrapper; -import freemarker.template.TemplateCollectionModel; -import freemarker.template.TemplateModel; -import freemarker.template.TemplateModelException; -import freemarker.template.TemplateModelIterator; -import freemarker.template.TemplateSequenceModel; - -/** - * <p>A class that will wrap an arbitrary array into {@link TemplateCollectionModel} - * and {@link TemplateSequenceModel} interfaces. It supports element retrieval through the <tt>array[index]</tt> - * syntax and can be iterated as a list. - */ -public class ArrayModel -extends - BeanModel -implements - TemplateCollectionModel, - TemplateSequenceModel { - static final ModelFactory FACTORY = - new ModelFactory() - { - public TemplateModel create(Object object, ObjectWrapper wrapper) { - return new ArrayModel(object, (BeansWrapper) wrapper); - } - }; - - // Cached length of the array - private final int length; - - /** - * Creates a new model that wraps the specified array object. - * @param array the array object to wrap into a model. - * @param wrapper the {@link BeansWrapper} associated with this model. - * Every model has to have an associated {@link BeansWrapper} instance. The - * model gains many attributes from its wrapper, including the caching - * behavior, method exposure level, method-over-item shadowing policy etc. - * @throws IllegalArgumentException if the passed object is not a Java array. - */ - public ArrayModel(Object array, BeansWrapper wrapper) { - super(array, wrapper); - Class clazz = array.getClass(); - if (!clazz.isArray()) - throw new IllegalArgumentException("Object is not an array, it's " + array.getClass().getName()); - length = Array.getLength(array); - } - - - public TemplateModelIterator iterator() { - return new Iterator(); - } - - public TemplateModel get(int index) - throws TemplateModelException { - try { - return wrap(Array.get(object, index)); - } catch (IndexOutOfBoundsException e) { - return null; -// throw new TemplateModelException("Index out of bounds: " + index); - } - } - - private class Iterator - implements - TemplateSequenceModel, - TemplateModelIterator { - private int position = 0; - - public boolean hasNext() { - return position < length; - } - - public TemplateModel get(int index) - throws TemplateModelException { - return ArrayModel.this.get(index); - } - - public TemplateModel next() - throws TemplateModelException { - return position < length ? get(position++) : null; - } - - public int size() { - return ArrayModel.this.size(); - } - } - - @Override - public int size() { - return length; - } - - @Override - public boolean isEmpty() { - return length == 0; - } -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/freemarker/ext/beans/BeanModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/ext/beans/BeanModel.java b/src/main/java/freemarker/ext/beans/BeanModel.java deleted file mode 100644 index 1d6062f..0000000 --- a/src/main/java/freemarker/ext/beans/BeanModel.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * 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.beans.IndexedPropertyDescriptor; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import freemarker.core.CollectionAndSequence; -import freemarker.core._DelayedFTLTypeDescription; -import freemarker.core._DelayedJQuote; -import freemarker.core._TemplateModelException; -import freemarker.ext.util.ModelFactory; -import freemarker.ext.util.WrapperTemplateModel; -import freemarker.template.AdapterTemplateModel; -import freemarker.template.ObjectWrapper; -import freemarker.template.SimpleScalar; -import freemarker.template.SimpleSequence; -import freemarker.template.TemplateCollectionModel; -import freemarker.template.TemplateHashModelEx; -import freemarker.template.TemplateModel; -import freemarker.template.TemplateModelException; -import freemarker.template.TemplateModelIterator; -import freemarker.template.TemplateModelWithAPISupport; -import freemarker.template.TemplateScalarModel; -import freemarker.template.utility.StringUtil; - -/** - * A class that will wrap an arbitrary object into {@link freemarker.template.TemplateHashModel} - * interface allowing calls to arbitrary property getters and invocation of - * accessible methods on the object from a template using the - * <tt>object.foo</tt> to access properties and <tt>object.bar(arg1, arg2)</tt> to - * invoke methods on it. You can also use the <tt>object.foo[index]</tt> syntax to - * access indexed properties. It uses Beans {@link java.beans.Introspector} - * to dynamically discover the properties and methods. - */ - -public class BeanModel -implements - TemplateHashModelEx, AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport { - - private static final Logger LOG = LoggerFactory.getLogger("freemarker.beans"); - - protected final Object object; - protected final BeansWrapper wrapper; - - // We use this to represent an unknown value as opposed to known value of null (JR) - static final TemplateModel UNKNOWN = new SimpleScalar("UNKNOWN"); - - static final ModelFactory FACTORY = - new ModelFactory() - { - @Override - public TemplateModel create(Object object, ObjectWrapper wrapper) { - return new BeanModel(object, (BeansWrapper) wrapper); - } - }; - - // I've tried to use a volatile ConcurrentHashMap field instead of HashMap + synchronized(this), but oddly it was - // a bit slower, at least on Java 8 u66. - private HashMap<Object, TemplateModel> memberCache; - - /** - * Creates a new model that wraps the specified object. Note that there are - * specialized subclasses of this class for wrapping arrays, collections, - * enumeration, iterators, and maps. Note also that the superclass can be - * used to wrap String objects if only scalar functionality is needed. You - * can also choose to delegate the choice over which model class is used for - * wrapping to {@link BeansWrapper#wrap(Object)}. - * @param object the object to wrap into a model. - * @param wrapper the {@link BeansWrapper} associated with this model. - * Every model has to have an associated {@link BeansWrapper} instance. The - * model gains many attributes from its wrapper, including the caching - * behavior, method exposure level, method-over-item shadowing policy etc. - */ - public BeanModel(Object object, BeansWrapper wrapper) { - // [2.4]: All models were introspected here, then the results was discareded, and get() will just do the - // introspection again. So is this necessary? (The inrospectNow parameter was added in 2.3.21 to allow - // lazy-introspecting BeansWrapper.trueModel|falseModel.) - this(object, wrapper, true); - } - - /** @since 2.3.21 */ - BeanModel(Object object, BeansWrapper wrapper, boolean inrospectNow) { - this.object = object; - this.wrapper = wrapper; - if (inrospectNow && object != null) { - // [2.4]: Could this be removed? - wrapper.getClassIntrospector().get(object.getClass()); - } - } - - /** - * Uses Beans introspection to locate a property or method with name - * matching the key name. If a method or property is found, it's wrapped - * into {@link freemarker.template.TemplateMethodModelEx} (for a method or - * indexed property), or evaluated on-the-fly and the return value wrapped - * into appropriate model (for a simple property) Models for various - * properties and methods are cached on a per-class basis, so the costly - * introspection is performed only once per property or method of a class. - * (Side-note: this also implies that any class whose method has been called - * will be strongly referred to by the framework and will not become - * unloadable until this class has been unloaded first. Normally this is not - * an issue, but can be in a rare scenario where you create many classes on- - * the-fly. Also, as the cache grows with new classes and methods introduced - * to the framework, it may appear as if it were leaking memory. The - * framework does, however detect class reloads (if you happen to be in an - * environment that does this kind of things--servlet containers do it when - * they reload a web application) and flushes the cache. If no method or - * property matching the key is found, the framework will try to invoke - * methods with signature - * <tt>non-void-return-type get(java.lang.String)</tt>, - * then <tt>non-void-return-type get(java.lang.Object)</tt>, or - * alternatively (if the wrapped object is a resource bundle) - * <tt>Object getObject(java.lang.String)</tt>. - * @throws TemplateModelException if there was no property nor method nor - * a generic <tt>get</tt> method to invoke. - */ - @Override - public TemplateModel get(String key) - throws TemplateModelException { - Class<?> clazz = object.getClass(); - Map<Object, Object> classInfo = wrapper.getClassIntrospector().get(clazz); - TemplateModel retval = null; - - try { - if (wrapper.isMethodsShadowItems()) { - Object fd = classInfo.get(key); - if (fd != null) { - retval = invokeThroughDescriptor(fd, classInfo); - } else { - retval = invokeGenericGet(classInfo, clazz, key); - } - } else { - TemplateModel model = invokeGenericGet(classInfo, clazz, key); - final TemplateModel nullModel = wrapper.wrap(null); - if (model != nullModel && model != UNKNOWN) { - return model; - } - Object fd = classInfo.get(key); - if (fd != null) { - retval = invokeThroughDescriptor(fd, classInfo); - if (retval == UNKNOWN && model == nullModel) { - // This is the (somewhat subtle) case where the generic get() returns null - // and we have no bean info, so we respect the fact that - // the generic get() returns null and return null. (JR) - retval = nullModel; - } - } - } - if (retval == UNKNOWN) { - if (wrapper.isStrict()) { - throw new InvalidPropertyException("No such bean property: " + key); - } else { - logNoSuchKey(key, classInfo); - } - retval = wrapper.wrap(null); - } - return retval; - } catch (TemplateModelException e) { - throw e; - } catch (Exception e) { - throw new _TemplateModelException(e, - "An error has occurred when reading existing sub-variable ", new _DelayedJQuote(key), - "; see cause exception! The type of the containing value was: ", - new _DelayedFTLTypeDescription(this) - ); - } - } - - private void logNoSuchKey(String key, Map<?, ?> keyMap) { - if (LOG.isDebugEnabled()) { - LOG.debug("Key " + StringUtil.jQuoteNoXSS(key) + " was not found on instance of " + - object.getClass().getName() + ". Introspection information for " + - "the class is: " + keyMap); - } - } - - /** - * Whether the model has a plain get(String) or get(Object) method - */ - - protected boolean hasPlainGetMethod() { - return wrapper.getClassIntrospector().get(object.getClass()).get(ClassIntrospector.GENERIC_GET_KEY) != null; - } - - private TemplateModel invokeThroughDescriptor(Object desc, Map<Object, Object> classInfo) - throws IllegalAccessException, InvocationTargetException, TemplateModelException { - // See if this particular instance has a cached implementation for the requested feature descriptor - TemplateModel cachedModel; - synchronized (this) { - cachedModel = memberCache != null ? memberCache.get(desc) : null; - } - - if (cachedModel != null) { - return cachedModel; - } - - TemplateModel resultModel = UNKNOWN; - if (desc instanceof IndexedPropertyDescriptor) { - Method readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod(); - resultModel = cachedModel = - new SimpleMethodModel(object, readMethod, - ClassIntrospector.getArgTypes(classInfo, readMethod), wrapper); - } else if (desc instanceof PropertyDescriptor) { - PropertyDescriptor pd = (PropertyDescriptor) desc; - resultModel = wrapper.invokeMethod(object, pd.getReadMethod(), null); - // cachedModel remains null, as we don't cache these - } else if (desc instanceof Field) { - resultModel = wrapper.wrap(((Field) desc).get(object)); - // cachedModel remains null, as we don't cache these - } else if (desc instanceof Method) { - Method method = (Method) desc; - resultModel = cachedModel = new SimpleMethodModel( - object, method, ClassIntrospector.getArgTypes(classInfo, method), wrapper); - } else if (desc instanceof OverloadedMethods) { - resultModel = cachedModel = new OverloadedMethodsModel( - object, (OverloadedMethods) desc, wrapper); - } - - // If new cachedModel was created, cache it - if (cachedModel != null) { - synchronized (this) { - if (memberCache == null) { - memberCache = new HashMap<Object, TemplateModel>(); - } - memberCache.put(desc, cachedModel); - } - } - return resultModel; - } - - void clearMemberCache() { - synchronized (this) { - memberCache = null; - } - } - - protected TemplateModel invokeGenericGet(Map/*<Object, Object>*/ classInfo, Class<?> clazz, String key) - throws IllegalAccessException, InvocationTargetException, - TemplateModelException { - Method genericGet = (Method) classInfo.get(ClassIntrospector.GENERIC_GET_KEY); - if (genericGet == null) { - return UNKNOWN; - } - - return wrapper.invokeMethod(object, genericGet, new Object[] { key }); - } - - protected TemplateModel wrap(Object obj) - throws TemplateModelException { - return wrapper.getOuterIdentity().wrap(obj); - } - - protected Object unwrap(TemplateModel model) - throws TemplateModelException { - return wrapper.unwrap(model); - } - - /** - * Tells whether the model is empty. It is empty if either the wrapped - * object is null, or it's a Boolean with false value. - */ - @Override - public boolean isEmpty() { - if (object instanceof String) { - return ((String) object).length() == 0; - } - if (object instanceof Collection) { - return ((Collection<?>) object).isEmpty(); - } - if (object instanceof Iterator && wrapper.is2324Bugfixed()) { - return !((Iterator<?>) object).hasNext(); - } - if (object instanceof Map) { - return ((Map<?,?>) object).isEmpty(); - } - return object == null || Boolean.FALSE.equals(object); - } - - /** - * Returns the same as {@link #getWrappedObject()}; to ensure that, this method will be final starting from 2.4. - * This behavior of {@link BeanModel} is assumed by some FreeMarker code. - */ - @Override - public Object getAdaptedObject(Class<?> hint) { - return object; // return getWrappedObject(); starting from 2.4 - } - - @Override - public Object getWrappedObject() { - return object; - } - - @Override - public int size() { - return wrapper.getClassIntrospector().keyCount(object.getClass()); - } - - @Override - public TemplateCollectionModel keys() { - return new CollectionAndSequence(new SimpleSequence(keySet(), wrapper)); - } - - @Override - public TemplateCollectionModel values() throws TemplateModelException { - List<Object> values = new ArrayList<Object>(size()); - TemplateModelIterator it = keys().iterator(); - while (it.hasNext()) { - String key = ((TemplateScalarModel) it.next()).getAsString(); - values.add(get(key)); - } - return new CollectionAndSequence(new SimpleSequence(values, wrapper)); - } - - @Override - public String toString() { - return object.toString(); - } - - /** - * Helper method to support TemplateHashModelEx. Returns the Set of - * Strings which are available via the TemplateHashModel - * interface. Subclasses that override <tt>invokeGenericGet</tt> to - * provide additional hash keys should also override this method. - */ - protected Set/*<Object>*/ keySet() { - return wrapper.getClassIntrospector().keySet(object.getClass()); - } - - @Override - public TemplateModel getAPI() throws TemplateModelException { - return wrapper.wrapAsAPI(object); - } - -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/freemarker/ext/beans/BeansModelCache.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/ext/beans/BeansModelCache.java b/src/main/java/freemarker/ext/beans/BeansModelCache.java deleted file mode 100644 index 659fe1b..0000000 --- a/src/main/java/freemarker/ext/beans/BeansModelCache.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import freemarker.ext.util.ModelCache; -import freemarker.ext.util.ModelFactory; -import freemarker.template.TemplateModel; - -public class BeansModelCache extends ModelCache { - private final Map classToFactory = new ConcurrentHashMap(); - private final Set mappedClassNames = new HashSet(); - - private final BeansWrapper wrapper; - - BeansModelCache(BeansWrapper wrapper) { - this.wrapper = wrapper; - } - - @Override - protected boolean isCacheable(Object object) { - return object.getClass() != Boolean.class; - } - - @Override - @SuppressFBWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER", justification="Locks for factory creation only") - protected TemplateModel create(Object object) { - Class clazz = object.getClass(); - - ModelFactory factory = (ModelFactory) classToFactory.get(clazz); - - if (factory == null) { - // Synchronized so that we won't unnecessarily create the same factory for multiple times in parallel. - synchronized (classToFactory) { - factory = (ModelFactory) classToFactory.get(clazz); - if (factory == null) { - String className = clazz.getName(); - // clear mappings when class reloading is detected - if (!mappedClassNames.add(className)) { - classToFactory.clear(); - mappedClassNames.clear(); - mappedClassNames.add(className); - } - factory = wrapper.getModelFactory(clazz); - classToFactory.put(clazz, factory); - } - } - } - - return factory.create(object, wrapper); - } -}
