blob: abcf4e64d52df6980e9292913df378452ae2f192 [file] [log] [blame]
/*
* Copyright (c) 2003-2007 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.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.UndeclaredThrowableException;
class MethodUtilities
{
// Get rid of these on Java 5
private static final Method METHOD_IS_VARARGS = getIsVarArgsMethod(Method.class);
private static final Method CONSTRUCTOR_IS_VARARGS = getIsVarArgsMethod(Constructor.class);
/**
* Determines whether the type given as the 1st argument is convertible to the type given as the 2nd argument
* for method call argument conversion. This follows the rules of the Java reflection-based method call, except
* that since we don't have the value here, a boxed calls is never seens as convertable to a primtivie type.
*
* @return 0 means {@code false}, non-0 means {@code true}.
* That is, 0 is returned less specificity or incomparable specificity, also when if
* then method was aborted because of {@code ifHigherThan}.
* The absolute value of the returned non-0 number symbolizes how more specific it is:
* <ul>
* <li>1: The two classes are identical</li>
* <li>2: The 1st type is primitive, the 2nd type is the corresponding boxing class</li>
* <li>3: Both classes are numerical, and one is convertible into the other with widening conversion.
* E.g., {@code int} is convertible to {@code long} and {#code double}, hence {@code int} is more
* specific.
* This ignores primitive VS boxed mismatches, except that a boxed class is never seen as
* convertible to a primitive class.</li>
* <li>4: One class is {@code instanceof} of the other, but they aren't identical.
* But unlike in Java, primitive numerical types are {@code instanceof} {@link Number} here.</li>
* </ul>
*/
static int isMoreOrSameSpecificParameterType(final Class specific, final Class generic, boolean bugfixed,
int ifHigherThan) {
if (ifHigherThan >= 4) return 0;
if(generic.isAssignableFrom(specific)) {
// Identity or widening reference conversion:
return generic == specific ? 1 : 4;
} else {
final boolean specificIsPrim = specific.isPrimitive();
final boolean genericIsPrim = generic.isPrimitive();
if (specificIsPrim) {
if (genericIsPrim) {
if (ifHigherThan >= 3) return 0;
return isWideningPrimitiveNumberConversion(specific, generic) ? 3 : 0;
} else { // => specificIsPrim && !genericIsPrim
if (bugfixed) {
final Class specificAsBoxed = ClassUtil.primitiveClassToBoxingClass(specific);
if (specificAsBoxed == generic) {
// A primitive class is more specific than its boxing class, because it can't store null
return 2;
} else if (generic.isAssignableFrom(specificAsBoxed)) {
// Note: This only occurs if `specific` is a primitive numerical, and `generic == Number`
return 4;
} else if (ifHigherThan >= 3) {
return 0;
} else if (Number.class.isAssignableFrom(specificAsBoxed)
&& Number.class.isAssignableFrom(generic)) {
return isWideningBoxedNumberConversion(specificAsBoxed, generic) ? 3 : 0;
} else {
return 0;
}
} else {
return 0;
}
}
} else { // => !specificIsPrim
if (ifHigherThan >= 3) return 0;
if (bugfixed && !genericIsPrim
&& Number.class.isAssignableFrom(specific) && Number.class.isAssignableFrom(generic)) {
return isWideningBoxedNumberConversion(specific, generic) ? 3 : 0;
} else {
return 0;
}
}
} // of: !generic.isAssignableFrom(specific)
}
private static boolean isWideningPrimitiveNumberConversion(final Class source, final Class target) {
// Check for widening primitive numerical conversion.
if(target == Short.TYPE && (source == Byte.TYPE)) {
return true;
} else if (target == Integer.TYPE &&
(source == Short.TYPE || source == Byte.TYPE)) {
return true;
} else if (target == Long.TYPE &&
(source == Integer.TYPE || source == Short.TYPE ||
source == Byte.TYPE)) {
return true;
} else if (target == Float.TYPE &&
(source == Long.TYPE || source == Integer.TYPE ||
source == Short.TYPE || source == Byte.TYPE)) {
return true;
} else if (target == Double.TYPE &&
(source == Float.TYPE || source == Long.TYPE ||
source == Integer.TYPE || source == Short.TYPE ||
source == Byte.TYPE)) {
return true;
} else {
return false;
}
}
private static boolean isWideningBoxedNumberConversion(final Class source, final Class target) {
// Check for widening primitive numerical conversion.
if(target == Short.class && (source == Byte.class)) {
return true;
} else if (target == Integer.class &&
(source == Short.class || source == Byte.class)) {
return true;
} else if (target == Long.class &&
(source == Integer.class || source == Short.class ||
source == Byte.class)) {
return true;
} else if (target == Float.class &&
(source == Long.class || source == Integer.class ||
source == Short.class || source == Byte.class)) {
return true;
} else if (target == Double.class &&
(source == Float.class || source == Long.class ||
source == Integer.class || source == Short.class ||
source == Byte.class)) {
return true;
} else {
return false;
}
}
/**
* Attention, this doesn't handle primitive classes correctly, nor numerical conversions.
*/
static Set getAssignables(Class c1, Class c2) {
Set s = new HashSet();
collectAssignables(c1, c2, s);
return s;
}
private static void collectAssignables(Class c1, Class c2, Set s) {
if(c1.isAssignableFrom(c2)) {
s.add(c1);
}
Class sc = c1.getSuperclass();
if(sc != null) {
collectAssignables(sc, c2, s);
}
Class[] itf = c1.getInterfaces();
for(int i = 0; i < itf.length; ++i) {
collectAssignables(itf[i], c2, s);
}
}
static Class[] getParameterTypes(Member member) {
if(member instanceof Method) {
return ((Method)member).getParameterTypes();
}
if(member instanceof Constructor) {
return ((Constructor)member).getParameterTypes();
}
throw new RuntimeException();
}
static boolean isVarArgs(Member member) {
if(member instanceof Method) {
return isVarArgs(member, METHOD_IS_VARARGS);
}
if(member instanceof Constructor) {
return isVarArgs(member, CONSTRUCTOR_IS_VARARGS);
}
throw new RuntimeException();
}
private static boolean isVarArgs(Member member, Method isVarArgsMethod) {
if(isVarArgsMethod == null) {
return false;
}
try {
return ((Boolean)isVarArgsMethod.invoke(member, (Object[]) null)).booleanValue();
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new UndeclaredThrowableException(e);
}
}
private static Method getIsVarArgsMethod(Class memberClass) {
try {
return memberClass.getMethod("isVarArgs", (Class[]) null);
}
catch(NoSuchMethodException e) {
return null; // pre 1.5 JRE
}
}
}