/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2002 - 2007 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated
* and its suppliers and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package flex.messaging.util;
import flex.messaging.io.TypeMarshaller;
import flex.messaging.io.TypeMarshallingContext;
import flex.messaging.MessageException;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.lang.reflect.Method;
/**
* A utility class used to find a suitable method based on matching
* signatures to the types of set of arguments. Since the arguments
* may be from more loosely typed environments such as ActionScript,
* a translator can be employed to handle type conversion. Note that
* there isn't a great guarantee for which method will be selected
* when several overloaded methods match very closely through the use
* of various combinations of generic types.
*
* @exclude
*/
public class MethodMatcher
{
private final Map methodCache = new HashMap();
private static final int ARGUMENT_CONVERSION_ERROR = 10006;
private static final int CANNOT_INVOKE_METHOD = 10007;
/**
* Default constructor.
*/
public MethodMatcher()
{
}
/**
* Utility method that searches a class for a given method, taking
* into account the supplied parameters when evaluating overloaded
* method signatures.
*
* @param c the class
* @param methodName desired method to search for
* @param parameters required to distinguish between overloaded methods of the same name
* @return The best-match <tt>Method</tt>.
*/
public Method getMethod(Class c, String methodName, List parameters)
{
Method method = null;
// Keep track of the best method match found
Match bestMatch = new Match(methodName);
// Determine supplied parameter types.
Class[] suppliedParamTypes = paramTypes(parameters);
// Create a key to search our method cache
MethodKey methodKey = new MethodKey(c, methodName, suppliedParamTypes);
if (methodCache.containsKey(methodKey))
{
method = (Method) methodCache.get(methodKey);
String thisMethodName = method.getName();
bestMatch.matchedMethodName = thisMethodName;
// Despite the method being cached, we still have to convert
// input params to the desired types each invocation...
Class[] desiredParamTypes = method.getParameterTypes();
bestMatch.methodParamTypes = desiredParamTypes;
convertParams(parameters, desiredParamTypes, bestMatch);
}
else
{
// Search for the method by name
Method[] methods = c.getMethods();
for (int i = 0; i < methods.length; i++)
{
Method thisMethod = methods[i];
String thisMethodName = thisMethod.getName();
// FIXME: Do we want to do this case-insensitively in Flex 2.0?
// First, search by name; for backwards compatibility
// we continue to check case-insensitively
if (thisMethodName.equalsIgnoreCase(methodName))
{
// Next, search on params
Match currentMatch = new Match(methodName);
currentMatch.matchedMethodName = thisMethodName;
// If we've not yet had a match, this is our
// best match so far...
if (bestMatch.matchedMethodName == null)
{
bestMatch = currentMatch;
}
// Number of parameters must match
Class[] desiredParamTypes = thisMethod.getParameterTypes();
currentMatch.methodParamTypes = desiredParamTypes;
if (desiredParamTypes.length == suppliedParamTypes.length)
{
currentMatch.matchedByNumberOfParams = true;
// If we've not yet matched any params
// this is our best match so far...
if (!bestMatch.matchedByNumberOfParams && bestMatch.matchedParamCount == 0)
{
bestMatch = currentMatch;
}
// Parameter types must also be compatible
convertParams(parameters, desiredParamTypes, currentMatch);
// If we've not yet had this many params
// match, this is our best match so far...
if (currentMatch.matchedParamCount >= bestMatch.matchedParamCount)
{
bestMatch = currentMatch;
}
// If all types were compatible, we have a match
if (currentMatch.matchedParamCount == desiredParamTypes.length)
{
method = thisMethod;
bestMatch = currentMatch;
synchronized(methodCache)
{
Method method2 = (Method)methodCache.get(methodKey);
if (method2 == null)
methodCache.put(methodKey, method);
else
method = method2;
}
break;
}
}
}
}
}
if (method == null)
{
methodNotFound(methodName, suppliedParamTypes, bestMatch);
}
else if (bestMatch.paramTypeConversionFailure != null)
{
//Error occurred while attempting to convert an input argument's type.
MessageException me = new MessageException();
me.setMessage(ARGUMENT_CONVERSION_ERROR);
me.setCode("Server.Processing");
me.setRootCause(bestMatch.paramTypeConversionFailure);
throw me;
}
return method;
}
/**
* Utility method to convert a collection of parameters to desired types. We keep track
* of the progress of the conversion to allow callers to gauge the success of the conversion.
* This is important for ranking overloaded-methods and debugging purposes.
*
* @param parameters actual parameters for an invocation
* @param desiredParamTypes classes in the signature of a potential match for the invocation
* @param currentMatch the currently best known match
*/
public static void convertParams(List parameters, Class[] desiredParamTypes, Match currentMatch)
{
int matchCount = 0;
currentMatch.matchedParamCount = 0;
currentMatch.convertedSuppliedTypes = new Class[desiredParamTypes.length];
TypeMarshaller marshaller = TypeMarshallingContext.getTypeMarshaller();
for (int i = 0; i < desiredParamTypes.length; i++)
{
Object param = parameters.get(i);
// consider null param to match
if (param != null)
{
Object obj = null;
Class objClass = null;
if (marshaller != null)
{
try
{
obj = marshaller.convert(param, desiredParamTypes[i]);
}
catch (MessageException ex)
{
currentMatch.paramTypeConversionFailure = ex;
break;
}
}
else
{
obj = param;
}
currentMatch.convertedSuppliedTypes[i] = (obj != null ? (objClass = obj.getClass()) : null);
// things match if we now have an object which is assignable from the
// method param class type or if we have an Object which corresponds to
// a primitive
if (obj == null ||
(desiredParamTypes[i].isAssignableFrom(objClass)) ||
(desiredParamTypes[i] == Integer.TYPE && Integer.class.isAssignableFrom(objClass)) ||
(desiredParamTypes[i] == Double.TYPE && Double.class.isAssignableFrom(objClass)) ||
(desiredParamTypes[i] == Long.TYPE && Long.class.isAssignableFrom(objClass)) ||
(desiredParamTypes[i] == Boolean.TYPE && Boolean.class.isAssignableFrom(objClass)) ||
(desiredParamTypes[i] == Character.TYPE && Character.class.isAssignableFrom(objClass)) ||
(desiredParamTypes[i] == Float.TYPE && Float.class.isAssignableFrom(objClass)) ||
(desiredParamTypes[i] == Short.TYPE && Short.class.isAssignableFrom(objClass)) ||
(desiredParamTypes[i] == Byte.TYPE && Byte.class.isAssignableFrom(objClass)))
{
parameters.set(i, obj);
matchCount++;
}
else
{
break;
}
}
else
{
matchCount++;
}
}
currentMatch.matchedParamCount = matchCount;
}
/**
* Utility method that iterates over a collection of input
* parameters to determine their types while logging
* the class names to create a unique identifier for a
* method signature.
*
* @param parameters - A list of supplied parameters.
* @return An array of <tt>Class</tt> instances indicating the class of each corresponding parameter.
*/
public static Class[] paramTypes(List parameters)
{
Class[] paramTypes = new Class[parameters.size()];
for (int i = 0; i < paramTypes.length; i++)
{
Object p = parameters.get(i);
paramTypes[i] = p == null ? Object.class : p.getClass();
}
return paramTypes;
}
/**
* Utility method to provide more detailed information in the event that a search
* for a specific method failed for the service class.
*
* @param methodName the name of the missing method
* @param suppliedParamTypes the types of parameters supplied for the search
* @param bestMatch the best match found during the search
*/
public static void methodNotFound(String methodName, Class[] suppliedParamTypes, Match bestMatch)
{
// Set default error message...
// Cannot invoke method '{methodName}'.
int errorCode = CANNOT_INVOKE_METHOD;
Object[] errorParams = new Object[]{methodName};
String errorDetailVariant = "0";
// Method '{methodName}' not found.
Object[] errorDetailParams = new Object[]{methodName};
if (bestMatch.matchedMethodName != null)
{
// Cannot invoke method '{bestMatch.matchedMethodName}'.
errorCode = CANNOT_INVOKE_METHOD;
errorParams = new Object[]{bestMatch.matchedMethodName};
int suppliedParamCount = suppliedParamTypes.length;
int expectedParamCount = bestMatch.methodParamTypes != null ? bestMatch.methodParamTypes.length : 0;
if (suppliedParamCount != expectedParamCount)
{
// {suppliedParamCount} arguments were sent but {expectedParamCount} were expected.
errorDetailVariant = "1";
errorDetailParams = new Object[]{new Integer(suppliedParamCount), new Integer(expectedParamCount)};
}
else
{
String suppliedTypes = bestMatch.listTypes(suppliedParamTypes);
String convertedTypes = bestMatch.listConvertedTypes();
String expectedTypes = bestMatch.listExpectedTypes();
if (expectedTypes != null)
{
if (suppliedTypes != null)
{
if (convertedTypes != null)
{
// The expected argument types are ({expectedTypes})
// but the supplied types were ({suppliedTypes})
// and converted to ({convertedTypes}).
errorDetailVariant = "2";
errorDetailParams = new Object[]{expectedTypes, suppliedTypes, convertedTypes};
}
else
{
// The expected argument types are ({expectedTypes})
// but the supplied types were ({suppliedTypes})
// with none successfully converted.
errorDetailVariant = "3";
errorDetailParams = new Object[]{expectedTypes, suppliedTypes};
}
}
else
{
// The expected argument types are ({expectedTypes})
// but no arguments were provided.
errorDetailVariant = "4";
errorDetailParams = new Object[]{expectedTypes};
}
}
else
{
// No arguments were expected but the following types were supplied (suppliedTypes)
errorDetailVariant = "5";
errorDetailParams = new Object[]{suppliedTypes};
}
}
}
MessageException ex = new MessageException();
ex.setMessage(errorCode, errorParams);
ex.setCode(MessageException.CODE_SERVER_RESOURCE_UNAVAILABLE);
if (errorDetailVariant != null)
ex.setDetails(errorCode, errorDetailVariant, errorDetailParams);
if (bestMatch.paramTypeConversionFailure != null)
ex.setRootCause(bestMatch.paramTypeConversionFailure);
throw ex;
}
/**
* A utility class to help rank methods in the search
* for a best match, given a name and collection of
* input parameters.
*/
public static class Match
{
/**
* Constructor.
* @param name the name of the method to match
*/
public Match(String name)
{
this.methodName = name;
}
/**
* Returns true if desired and found method names match.
* @return true if desired and found method names match
*/
public boolean matchedExactlyByName()
{
if (matchedMethodName != null)
return matchedMethodName.equals(methodName);
else
return false;
}
/**
* Returns true if desired and found method names match only when case is ignored.
* @return true if desired and found method names match only when case is ignored
*/
public boolean matchedLooselyByName()
{
if (matchedMethodName != null)
return (!matchedExactlyByName() && matchedMethodName.equalsIgnoreCase(methodName));
else
return false;
}
/**
* Lists the classes in the signature of the method matched.
* @return the classes in the signature of the method matched
*/
public String listExpectedTypes()
{
return listTypes(methodParamTypes);
}
/**
* Lists the classes corresponding to actual invocation parameters once they have been
* converted as best they could to match the classes in the invoked method's signature.
*
* @return the classes corresponding to actual invocation parameters once they have been
* converted as best they could to match the classes in the invoked method's signature
*/
public String listConvertedTypes()
{
return listTypes(convertedSuppliedTypes);
}
/**
* Creates a string representation of the class names in the array of types passed into
* this method.
*
* @param types an array of types whose names are to be listed
* @return a string representation of the class names in the array of types
*/
public String listTypes(Class[] types)
{
if (types != null && types.length > 0)
{
StringBuffer sb = new StringBuffer();
for (int i = 0; i < types.length; i++)
{
if (i > 0)
sb.append(", ");
Class c = types[i];
if (c != null)
{
if (c.isArray())
{
c = c.getComponentType();
sb.append(c.getName()).append("[]");
}
else
{
sb.append(c.getName());
}
}
else
sb.append("null");
}
return sb.toString();
}
return null;
}
final String methodName;
String matchedMethodName;
boolean matchedByNumberOfParams;
int matchedParamCount;
Class[] methodParamTypes;
Class[] convertedSuppliedTypes;
Exception paramTypeConversionFailure;
}
}