/*
* 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.io.
Serializable;
import java.util.
ArrayList;
import java.util.
Iterator;
import java.util.
LinkedList;
import java.util.
List;
import java.util.
Map;
import java.util.
Set;
import java.util.concurrent.
ConcurrentHashMap;
import
edu.
umd.
cs.
findbugs.
annotations.
SuppressFBWarnings;
import freemarker.template.
TemplateModelException;
import freemarker.template.utility.
ClassUtil;
import freemarker.template.utility.
NullArgumentException;
/**
* Encapsulates the rules and data structures (including cache) for choosing of the best matching callable member for
* a parameter list, from a given set of callable members. There are two subclasses of this, one for non-varags methods,
* and one for varargs methods.
*/
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 static final int[][]
ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY = new int[1][];
static {
ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY[0] =
ALL_ZEROS_ARRAY;
}
private
Class[/*number of args*/][/*arg index*/]
unwrappingHintsByParamCount;
/**
* Tells what types occur at a given parameter position with a bit field. See {@link TypeFlags}.
*/
private int[/*number of args*/][/*arg index*/]
typeFlagsByParamCount;
// TODO: This can cause memory-leak when classes are re-loaded. However, first the genericClassIntrospectionCache
// and such need to be fixed in this regard.
private final
Map/*<ArgumentTypes, MaybeEmptyCallableMemberDescriptor>*/
argTypesToMemberDescCache
= new
ConcurrentHashMap(6, 0.75f, 1);
private final
List/*<ReflectionCallableMemberDescriptor>*/
memberDescs = new
LinkedList();
/** Enables 2.3.21 {@link BeansWrapper} incompatibleImprovements */
protected final boolean
bugfixed;
OverloadedMethodsSubset(boolean
bugfixed) {
this.
bugfixed =
bugfixed;
}
void
addCallableMemberDescriptor(
ReflectionCallableMemberDescriptor 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] =
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] =
prepedParamTypes.
clone();
} else {
Class[]
unwrappingHints =
unwrappingHintsByParamCount[
paramCount];
if (
unwrappingHints == null) {
unwrappingHintsByParamCount[
paramCount] =
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 we had to unwrap with all possible target types of each parameter position,
// which would be slow and its result would be uncacheable (as we don't have anything usable as
// a lookup key). So we just use this compromise.
unwrappingHints[
paramIdx] =
getCommonSupertypeForUnwrappingHint(
unwrappingHints[
paramIdx],
prepedParamTypes[
paramIdx]);
}
}
}
int[]
typeFlagsByParamIdx =
ALL_ZEROS_ARRAY;
if (
bugfixed) {
// Fill typeFlagsByParamCount (if necessary)
for (int
paramIdx = 0;
paramIdx <
paramCount;
paramIdx++) {
final int
typeFlags =
TypeFlags.
classToTypeFlags(
prepedParamTypes[
paramIdx]);
if (
typeFlags != 0) {
if (
typeFlagsByParamIdx ==
ALL_ZEROS_ARRAY) {
typeFlagsByParamIdx = new int[
paramCount];
}
typeFlagsByParamIdx[
paramIdx] =
typeFlags;
}
}
mergeInTypesFlags(
paramCount,
typeFlagsByParamIdx);
}
afterWideningUnwrappingHints(
bugfixed ?
prepedParamTypes :
unwrappingHintsByParamCount[
paramCount],
typeFlagsByParamIdx);
}
Class[][]
getUnwrappingHintsByParamCount() {
return
unwrappingHintsByParamCount;
}
@
SuppressFBWarnings(
value="JLM_JSR166_UTILCONCURRENT_MONITORENTER",
justification="Locks for member descriptor creation only")
final
MaybeEmptyCallableMemberDescriptor getMemberDescriptorForArgs(
Object[]
args, boolean
varArg) {
ArgumentTypes argTypes = new
ArgumentTypes(
args,
bugfixed);
MaybeEmptyCallableMemberDescriptor memberDesc
= (
MaybeEmptyCallableMemberDescriptor)
argTypesToMemberDescCache.
get(
argTypes);
if (
memberDesc == null) {
// Synchronized so that we won't unnecessarily create the same member desc. for multiple times in parallel.
synchronized (
argTypesToMemberDescCache) {
memberDesc = (
MaybeEmptyCallableMemberDescriptor)
argTypesToMemberDescCache.
get(
argTypes);
if (
memberDesc == null) {
memberDesc =
argTypes.
getMostSpecific(
memberDescs,
varArg);
argTypesToMemberDescCache.
put(
argTypes,
memberDesc);
}
}
}
return
memberDesc;
}
Iterator/*<ReflectionCallableMemberDescriptor>*/
getMemberDescriptors() {
return
memberDescs.
iterator();
}
abstract
Class[]
preprocessParameterTypes(
CallableMemberDescriptor memberDesc);
abstract void
afterWideningUnwrappingHints(
Class[]
paramTypes, int[]
paramNumericalTypes);
abstract
MaybeEmptyMemberAndArguments getMemberAndArguments(
List/*<TemplateModel>*/
tmArgs,
BeansWrapper unwrapper) 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 =
_MethodUtil.
getAssignables(
c1,
c2);
commonTypes.
retainAll(
_MethodUtil.
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 (
_MethodUtil.
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 (
_MethodUtil.
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 "type flags" of each parameter positions, or {@code null} if there's no method with this parameter
* count or if we are in pre-2.3.21 mode, or {@link #ALL_ZEROS_ARRAY} if there were no parameters that turned
* on a flag. The returned {@code int}-s are one or more {@link TypeFlags} constants binary "or"-ed together.
*/
final protected int[]
getTypeFlags(int
paramCount) {
return
typeFlagsByParamCount != null &&
typeFlagsByParamCount.length >
paramCount
?
typeFlagsByParamCount[
paramCount]
: null;
}
/**
* Updates the content of the {@link #typeFlagsByParamCount} field with the parameter type flags of a method.
* Don't call this when {@link #bugfixed} is {@code false}!
*
* @param dstParamCount The parameter count for which we want to merge in the type flags
* @param srcTypeFlagsByParamIdx If shorter than {@code dstParamCount}, its last item will be repeated until
* dstParamCount length is reached. If longer, the excessive items will be ignored.
* Maybe {@link #ALL_ZEROS_ARRAY}. Maybe a 0-length array. Can't be {@code null}.
*/
final protected void
mergeInTypesFlags(int
dstParamCount, int[]
srcTypeFlagsByParamIdx) {
NullArgumentException.
check("srcTypesFlagsByParamIdx",
srcTypeFlagsByParamIdx);
// Special case of 0 param count:
if (
dstParamCount == 0) {
if (
typeFlagsByParamCount == null) {
typeFlagsByParamCount =
ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY;
} else if (
typeFlagsByParamCount !=
ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY) {
typeFlagsByParamCount[0] =
ALL_ZEROS_ARRAY;
}
return;
}
// Ensure that typesFlagsByParamCount[dstParamCount] exists:
if (
typeFlagsByParamCount == null) {
typeFlagsByParamCount = new int[
dstParamCount + 1][];
} else if (
typeFlagsByParamCount.length <=
dstParamCount) {
int[][]
newTypeFlagsByParamCount = new int[
dstParamCount + 1][];
System.
arraycopy(
typeFlagsByParamCount, 0,
newTypeFlagsByParamCount, 0,
typeFlagsByParamCount.length);
typeFlagsByParamCount =
newTypeFlagsByParamCount;
}
int[]
dstTypeFlagsByParamIdx =
typeFlagsByParamCount[
dstParamCount];
if (
dstTypeFlagsByParamIdx == null) {
// This is the first method added with this number of params => no merging
if (
srcTypeFlagsByParamIdx !=
ALL_ZEROS_ARRAY) {
int
srcParamCount =
srcTypeFlagsByParamIdx.length;
dstTypeFlagsByParamIdx = new int[
dstParamCount];
for (int
paramIdx = 0;
paramIdx <
dstParamCount;
paramIdx++) {
dstTypeFlagsByParamIdx[
paramIdx]
=
srcTypeFlagsByParamIdx[
paramIdx <
srcParamCount ?
paramIdx :
srcParamCount - 1];
}
} else {
dstTypeFlagsByParamIdx =
ALL_ZEROS_ARRAY;
}
typeFlagsByParamCount[
dstParamCount] =
dstTypeFlagsByParamIdx;
} else {
// dstTypeFlagsByParamIdx != null, so we need to merge into it.
if (
srcTypeFlagsByParamIdx ==
dstTypeFlagsByParamIdx) {
// Used to occur when both are ALL_ZEROS_ARRAY
return;
}
// As we will write dstTypeFlagsByParamIdx, it can't remain ALL_ZEROS_ARRAY anymore.
if (
dstTypeFlagsByParamIdx ==
ALL_ZEROS_ARRAY &&
dstParamCount > 0) {
dstTypeFlagsByParamIdx = new int[
dstParamCount];
typeFlagsByParamCount[
dstParamCount] =
dstTypeFlagsByParamIdx;
}
for (int
paramIdx = 0;
paramIdx <
dstParamCount;
paramIdx++) {
final int
srcParamTypeFlags;
if (
srcTypeFlagsByParamIdx !=
ALL_ZEROS_ARRAY) {
int
srcParamCount =
srcTypeFlagsByParamIdx.length;
srcParamTypeFlags =
srcTypeFlagsByParamIdx[
paramIdx <
srcParamCount ?
paramIdx :
srcParamCount - 1];
} else {
srcParamTypeFlags = 0;
}
final int
dstParamTypesFlags =
dstTypeFlagsByParamIdx[
paramIdx];
if (
dstParamTypesFlags !=
srcParamTypeFlags) {
int
mergedTypeFlags =
dstParamTypesFlags |
srcParamTypeFlags;
if ((
mergedTypeFlags &
TypeFlags.
MASK_ALL_NUMERICALS) != 0) {
// Must not be set if we don't have numerical type at this index!
mergedTypeFlags |=
TypeFlags.
WIDENED_NUMERICAL_UNWRAPPING_HINT;
}
dstTypeFlagsByParamIdx[
paramIdx] =
mergedTypeFlags;
}
}
}
}
protected void
forceNumberArgumentsToParameterTypes(
Object[]
args,
Class[]
paramTypes, int[]
typeFlagsByParamIndex) {
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
typeFlags =
typeFlagsByParamIndex[
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 ((
typeFlags &
TypeFlags.
WIDENED_NUMERICAL_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;
}
}
}
}
}
}