blob: ccc18e70ca4bd2fc510c8dabcd21ebf234ff2301 [file] [log] [blame]
/*
* Copyright (c) 2003 The Visigoth Software Society. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowledgement:
* "This product includes software developed by the
* Visigoth Software Society (http://www.visigoths.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
* project contributors may be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact visigoths@visigoths.org.
*
* 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
* nor may "FreeMarker" or "Visigoth" appear in their names
* without prior written permission of the Visigoth Software Society.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Visigoth Software Society. For more
* information on the Visigoth Software Society, please see
* http://www.visigoths.org/
*/
package freemarker.ext.beans;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import freemarker.template.TemplateModelException;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.NullArgumentException;
/**
* @author Attila Szegedi
*/
abstract class OverloadedMethodsSubset {
/**
* Used for an optimization trick to substitute an array of whatever size that contains only 0-s. Since this array
* is 0 long, this means that the code that reads the int[] always have to check if the int[] has this value, and
* then it has to act like if was all 0-s.
*/
static final int[] ALL_ZEROS_ARRAY = new int[0];
private Class[/*number of args*/][/*arg index*/] unwrappingHintsByParamCount;
/**
* Tells what numerical types occur at a given parameter position with a bit field, also if the
* unwrapping will do proper conversion. See {@link OverloadedNumberUtil#FLAG_INTEGER}, etc.
*/
private int[/*number of args*/][/*arg index*/] possibleNumericalTypesByParamCount;
// TODO: This can cause memory-leak when classes are re-loaded. However, first the genericClassIntrospectionCache
// and such need to be fixed in this regard.
// Java 5: Use ConcurrentHashMap:
private final Map/*<ArgumentTypes, MaybeEmptyCallableMemberDescriptor>*/ argTypesToMemberDescCache = new HashMap();
private final List/*<CallableMemberDescriptor>*/ memberDescs = new LinkedList();
/** Enables 2.3.21 {@link BeansWrapper} incompatibleImprovements */
protected final boolean bugfixed;
OverloadedMethodsSubset(BeansWrapper beansWrapper) {
bugfixed = beansWrapper.is2321Bugfixed();
}
void addCallableMemberDescriptor(CallableMemberDescriptor memberDesc) {
memberDescs.add(memberDesc);
// Warning: Do not modify this array, or put it into unwrappingHintsByParamCount by reference, as the arrays
// inside that are modified!
final Class[] prepedParamTypes = preprocessParameterTypes(memberDesc);
final int paramCount = prepedParamTypes.length; // Must be the same as the length of the original param list
// Merge these unwrapping hints with the existing table of hints:
if (unwrappingHintsByParamCount == null) {
unwrappingHintsByParamCount = new Class[paramCount + 1][];
unwrappingHintsByParamCount[paramCount] = (Class[]) prepedParamTypes.clone();
} else if (unwrappingHintsByParamCount.length <= paramCount) {
Class[][] newUnwrappingHintsByParamCount = new Class[paramCount + 1][];
System.arraycopy(unwrappingHintsByParamCount, 0, newUnwrappingHintsByParamCount, 0,
unwrappingHintsByParamCount.length);
unwrappingHintsByParamCount = newUnwrappingHintsByParamCount;
unwrappingHintsByParamCount[paramCount] = (Class[]) prepedParamTypes.clone();
} else {
Class[] unwrappingHints = unwrappingHintsByParamCount[paramCount];
if (unwrappingHints == null) {
unwrappingHintsByParamCount[paramCount] = (Class[]) prepedParamTypes.clone();
} else {
for (int paramIdx = 0; paramIdx < prepedParamTypes.length; paramIdx++) {
// For each parameter list length, we merge the argument type arrays into a single Class[] that
// stores the most specific common types for each position. Hence we will possibly use a too generic
// hint for the unwrapping. For correct behavior, for each overloaded methods its own parameter
// types should be used as a hint. But without unwrapping the arguments, we couldn't select the
// overloaded method. So this is a circular reference problem. We could try selecting the
// method based on the wrapped value, but that's quite tricky, and the result of such selection
// is not cacheable (the TM types are not enough as cache key then). So we just use this
// compromise.
unwrappingHints[paramIdx] = getCommonSupertypeForUnwrappingHint(
unwrappingHints[paramIdx], prepedParamTypes[paramIdx]);
}
}
}
int[] paramNumericalTypes = ALL_ZEROS_ARRAY;
if (bugfixed) {
// Fill possibleNumericalTypesByParamCount (if necessary)
for (int paramIdx = 0; paramIdx < paramCount; paramIdx++) {
final int numType = OverloadedNumberUtil.classToTypeFlags(prepedParamTypes[paramIdx]);
if (numType != 0) { // It's a numerical type
if (paramNumericalTypes == ALL_ZEROS_ARRAY) {
paramNumericalTypes = new int[paramCount];
}
paramNumericalTypes[paramIdx] = numType;
}
}
mergeInNumericalTypes(paramCount, paramNumericalTypes);
}
afterWideningUnwrappingHints(
bugfixed ? prepedParamTypes : unwrappingHintsByParamCount[paramCount],
paramNumericalTypes);
}
Class[][] getUnwrappingHintsByParamCount() {
return unwrappingHintsByParamCount;
}
final MaybeEmptyCallableMemberDescriptor getMemberDescriptorForArgs(Object[] args, boolean varArg) {
ArgumentTypes argTypes = new ArgumentTypes(args, bugfixed);
MaybeEmptyCallableMemberDescriptor memberDesc;
synchronized(argTypesToMemberDescCache) {
memberDesc = (MaybeEmptyCallableMemberDescriptor) argTypesToMemberDescCache.get(argTypes);
if(memberDesc == null) {
memberDesc = argTypes.getMostSpecific(memberDescs, varArg);
argTypesToMemberDescCache.put(argTypes, memberDesc);
}
}
return memberDesc;
}
Iterator/*<CallableMemberDescriptor>*/ getMemberDescriptors() {
return memberDescs.iterator();
}
abstract Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc);
abstract void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes);
abstract MaybeEmptyMemberAndArguments getMemberAndArguments(List/*<TemplateModel>*/ tmArgs,
BeansWrapper w) throws TemplateModelException;
/**
* Returns the most specific common class (or interface) of two parameter types for the purpose of unwrapping.
* This is trickier than finding the most specific overlapping superclass of two classes, because:
* <ul>
* <li>It considers primitive classes as the subclasses of the boxing classes.</li>
* <li>If the only common class is {@link Object}, it will try to find a common interface. If there are more
* of them, it will start removing those that are known to be uninteresting as unwrapping hints.</li>
* </ul>
*
* @param c1 Parameter type 1
* @param c2 Parameter type 2
*/
protected Class getCommonSupertypeForUnwrappingHint(Class c1, Class c2) {
if(c1 == c2) return c1;
// This also means that the hint for (Integer, Integer) will be Integer, not just Number. This is consistent
// with how non-overloaded method hints work.
if (bugfixed) {
// c1 primitive class to boxing class:
final boolean c1WasPrim;
if (c1.isPrimitive()) {
c1 = ClassUtil.primitiveClassToBoxingClass(c1);
c1WasPrim = true;
} else {
c1WasPrim = false;
}
// c2 primitive class to boxing class:
final boolean c2WasPrim;
if (c2.isPrimitive()) {
c2 = ClassUtil.primitiveClassToBoxingClass(c2);
c2WasPrim = true;
} else {
c2WasPrim = false;
}
if (c1 == c2) {
// If it was like int and Integer, boolean and Boolean, etc., we return the boxing type (as that's the
// less specific, because it allows null.)
// (If it was two equivalent primitives, we don't get here, because of the 1st line of the method.)
return c1;
} else if (Number.class.isAssignableFrom(c1) && Number.class.isAssignableFrom(c2)) {
// We don't want the unwrapper to convert to a numerical super-type [*] as it's not yet known what the
// actual number type of the chosen method will be. We will postpone the actual numerical conversion
// until that, especially as some conversions (like fixed point to floating point) can be lossy.
// * Numerical super-type: Like long > int > short > byte.
return Number.class;
} else if (c1WasPrim || c2WasPrim) {
// At this point these all stand:
// - At least one of them was primitive
// - No more than one of them was numerical
// - They don't have the same wrapper (boxing) class
return Object.class;
}
// Falls through
} else { // old buggy behavior
if(c2.isPrimitive()) {
if(c2 == Byte.TYPE) c2 = Byte.class;
else if(c2 == Short.TYPE) c2 = Short.class;
else if(c2 == Character.TYPE) c2 = Character.class;
else if(c2 == Integer.TYPE) c2 = Integer.class;
else if(c2 == Float.TYPE) c2 = Float.class;
else if(c2 == Long.TYPE) c2 = Long.class;
else if(c2 == Double.TYPE) c2 = Double.class;
}
}
// We never get to this point if buxfixed is true and any of these stands:
// - One of classes was a primitive type
// - One of classes was a numerical type (either boxing type or primitive)
Set commonTypes = MethodUtilities.getAssignables(c1, c2);
commonTypes.retainAll(MethodUtilities.getAssignables(c2, c1));
if(commonTypes.isEmpty()) {
// Can happen when at least one of the arguments is an interface, as
// they don't have Object at the root of their hierarchy
return Object.class;
}
// Gather maximally specific elements. Yes, there can be more than one
// thank to interfaces. I.e., if you call this method for String.class
// and Number.class, you'll have Comparable, Serializable, and Object as
// maximal elements.
List max = new ArrayList();
listCommonTypes: for (Iterator commonTypesIter = commonTypes.iterator(); commonTypesIter.hasNext();) {
Class clazz = (Class)commonTypesIter.next();
for (Iterator maxIter = max.iterator(); maxIter.hasNext();) {
Class maxClazz = (Class)maxIter.next();
if(MethodUtilities.isMoreOrSameSpecificParameterType(maxClazz, clazz, false /*bugfixed [1]*/, 0) != 0) {
// clazz can't be maximal, if there's already a more specific or equal maximal than it.
continue listCommonTypes;
}
if(MethodUtilities.isMoreOrSameSpecificParameterType(clazz, maxClazz, false /*bugfixed [1]*/, 0) != 0) {
// If it's more specific than a currently maximal element,
// that currently maximal is no longer a maximal.
maxIter.remove();
}
// 1: We don't use bugfixed at the "[1]"-marked points because it's slower and doesn't make any
// difference here as it's ensured that nor c1 nor c2 is primitive or numerical. The bugfix has only
// affected the treatment of primitives and numerical types.
}
// If we get here, no current maximal is more specific than the
// current class, so clazz is a new maximal so far.
max.add(clazz);
}
if (max.size() > 1) { // we have an ambiguity
if (bugfixed) {
// Find the non-interface class
for (Iterator it = max.iterator(); it.hasNext();) {
Class maxCl = (Class) it.next();
if (!maxCl.isInterface()) {
if (maxCl != Object.class) { // This actually shouldn't ever happen, but to be sure...
// If it's not Object, we use it as the most specific
return maxCl;
} else {
// Otherwise remove Object, and we will try with the interfaces
it.remove();
}
}
}
// At this point we only have interfaces left.
// Try removing interfaces about which we know that they are useless as unwrapping hints:
max.remove(Cloneable.class);
if (max.size() > 1) { // Still have an ambiguity...
max.remove(Serializable.class);
if (max.size() > 1) { // Still had an ambiguity...
max.remove(Comparable.class);
if (max.size() > 1) {
return Object.class; // Still had an ambiguity... no luck.
}
}
}
} else {
return Object.class;
}
}
return (Class) max.get(0);
}
/**
* Gets the numerical type "flags" of each parameter positions, or {@code null} if there's no method with this
* parameter count or if we aren't in pre-2.3.21 mode, {@link #ALL_ZEROS_ARRAY} if there were no numerical
* parameters. The returned {@code int}-s are on or more {@link OverloadedNumberUtil}} {@code FLAG_...} constants
* binary "or"-ed together.
*/
final protected int[] getPossibleNumericalTypes(int paramCount) {
return possibleNumericalTypesByParamCount != null && possibleNumericalTypesByParamCount.length > paramCount
? possibleNumericalTypesByParamCount[paramCount]
: null;
}
/**
* @param dstParamCount The parameter count for which we want to merge in the possible numerical types
* @param srcNumTypesByParamIdx If shorter than {@code dstParamCount}, it's last item will be repeated until
* dstParamCount length is reached. If longer, the excessive items will be ignored.
* Maybe {@link #ALL_ZEROS_ARRAY}. Cant'be a 0-length array. Can't be {@code null}.
*/
final protected void mergeInNumericalTypes(int dstParamCount, int[] srcNumTypesByParamIdx) {
NullArgumentException.check("srcNumTypesByParamIdx", srcNumTypesByParamIdx);
if (dstParamCount == 0) return;
// Ensure that possibleNumericalTypesByParamCount[dstParamCount] exists:
if (possibleNumericalTypesByParamCount == null) {
possibleNumericalTypesByParamCount = new int[dstParamCount + 1][];
} else if (possibleNumericalTypesByParamCount.length <= dstParamCount) {
if (srcNumTypesByParamIdx == null) return;
int[][] newNumericalTypeByParamCount = new int[dstParamCount + 1][];
System.arraycopy(possibleNumericalTypesByParamCount, 0, newNumericalTypeByParamCount, 0,
possibleNumericalTypesByParamCount.length);
possibleNumericalTypesByParamCount = newNumericalTypeByParamCount;
}
final int srcParamCount = srcNumTypesByParamIdx.length;
int[] dstNumTypesByParamIdx = possibleNumericalTypesByParamCount[dstParamCount];
if (dstNumTypesByParamIdx == null) {
// This is the first method added with this number of params => no merging
if (srcNumTypesByParamIdx != ALL_ZEROS_ARRAY) {
dstNumTypesByParamIdx = new int[dstParamCount];
for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) {
dstNumTypesByParamIdx[paramIdx]
= srcNumTypesByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1];
}
} else {
dstNumTypesByParamIdx = ALL_ZEROS_ARRAY;
}
possibleNumericalTypesByParamCount[dstParamCount] = dstNumTypesByParamIdx;
} else {
// dstNumTypesByParamIdx != null, so we need to merge into it.
if (srcNumTypesByParamIdx == dstNumTypesByParamIdx) {
// Used to occur when both are ALL_ZEROS_ARRAY
return;
}
// As we will write dstNumTypesByParamIdx, it can't remain ALL_ZEROS_ARRAY anymore.
if (dstNumTypesByParamIdx == ALL_ZEROS_ARRAY && dstParamCount > 0) {
dstNumTypesByParamIdx = new int[dstParamCount];
possibleNumericalTypesByParamCount[dstParamCount] = dstNumTypesByParamIdx;
}
for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) {
final int srcParamNumTypes
= srcNumTypesByParamIdx != ALL_ZEROS_ARRAY
? srcNumTypesByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1]
: 0;
final int dstParamNumTypes = dstNumTypesByParamIdx[paramIdx];
if (dstParamNumTypes != srcParamNumTypes) {
dstNumTypesByParamIdx[paramIdx]
= dstParamNumTypes | srcParamNumTypes | OverloadedNumberUtil.FLAG_WIDENED_UNWRAPPING_HINT;
}
// Note that if a parameter is non-numerical, its paraNumTypes is 0. So if we have a non-number and
// a some kind of number (non-0), the merged result will be marked with FLAG_WIDENED_UNWRAPPING_HINT.
// (I.e., the same happens as with two different numerical types.)
}
}
}
protected void forceNumberArgumentsToParameterTypes(
Object[] args, Class[] paramTypes, int[] unwrappingNumTypesByParamIndex) {
final int paramTypesLen = paramTypes.length;
final int argsLen = args.length;
for (int argIdx = 0; argIdx < argsLen; argIdx++) {
final int paramTypeIdx = argIdx < paramTypesLen ? argIdx : paramTypesLen - 1;
final int unwrappingNumTypes = unwrappingNumTypesByParamIndex[paramTypeIdx];
// Forcing the number type can only be interesting if there are numerical parameter types on that index,
// and the unwrapping was not to an exact numerical type.
if (unwrappingNumTypes != 0
&& (unwrappingNumTypes & OverloadedNumberUtil.FLAG_WIDENED_UNWRAPPING_HINT) != 0) {
final Object arg = args[argIdx];
// If arg isn't a number, we can't do any conversions anyway, regardless of the param type.
if (arg instanceof Number) {
final Class targetType = paramTypes[paramTypeIdx];
final Number convertedArg = BeansWrapper.forceUnwrappedNumberToType(
(Number) arg, targetType, bugfixed);
if (convertedArg != null) {
args[argIdx] = convertedArg;
}
}
}
}
}
}