in freemarker-core/src/main/java/freemarker/ext/beans/OverloadedMethodsSubset.java [161:304]
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);
}