#region License
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed 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.
*/
#endregion
#region Imports
using System;
using System.Collections;
using System.Globalization;
using System.Reflection;
using Spring.Core;
using Spring.Core.TypeResolution;
using Spring.Objects;
using Spring.Util;
#endregion
namespace Spring.Objects.Support{
/// <summary>
/// Helper class allowing one to declaratively specify a method call for later invocation.
/// </summary>
/// <remarks>
/// <p>
/// Typically not used directly but via its subclasses such as
/// <see cref="Spring.Objects.Factory.Config.MethodInvokingFactoryObject"/>.
/// </p>
/// <p>
/// Usage: specify either the <see cref="MethodInvoker.TargetType"/> and
/// <see cref="MethodInvoker.TargetMethod"/> or the
/// <see cref="MethodInvoker.TargetObject"/> and
/// <see cref="MethodInvoker.TargetMethod"/> properties respectively, and
/// (optionally) any arguments to the method. Then call the
/// <see cref="MethodInvoker.Prepare"/> method to prepare the invoker.
/// Once prepared, the invoker can be invoked any number of times.
/// </p>
/// </remarks>
/// <example>
/// <p>
/// The following example uses the <see cref="MethodInvoker"/> class to invoke the
/// <c>ToString()</c> method on the <c>Foo</c> class using a mixture of both named and unnamed
/// arguments.
/// </p>
/// <code language="C#">
/// public class Foo
/// {
/// public string ToString(string name, int age, string address)
/// {
/// return string.Format("{0}, {1} years old, {2}", name, age, address);
/// }
///
/// public static void Main()
/// {
/// Foo foo = new Foo();
/// MethodInvoker invoker = new MethodInvoker();
/// invoker.Arguments = new object [] {"Kaneda", "18 Kaosu Gardens, Nakatani Drive, Okinanawa"};
/// invoker.AddNamedArgument("age", 29);
/// invoker.Prepare();
/// // at this point, the arguments that will be passed to the method invocation
/// // will have been resolved into the following ordered array : {"Kaneda", 29, "18 Kaosu Gardens, Nakatani Drive, Okinanawa"}
/// string details = (string) invoker.Invoke();
/// Console.WriteLine (details);
/// // will print out 'Kaneda, 29 years old, 18 Kaosu Gardens, Nakatani Drive, Okinanawa'
/// }
/// }
/// </code>
/// </example>
/// <author>Colin Sampaleanu</author>
/// <author>Juergen Hoeller</author>
/// <author>Simon White (.NET)</author>
public class MethodInvoker
{
#region Fields
/// <summary>
/// The value returned from the invocation of a method that returns void.
/// </summary>
public static readonly Missing Void = Missing.Value;
private Type _targetType;
private object _targetObject;
private string _targetMethod;
private object[] _arguments;
private IDictionary _namedArguments;
private object[] _preparedArguments;
/// <summary>
/// The method that will be invoked.
/// </summary>
private MethodInfo _methodObject;
/// <summary>
/// The <see cref="System.Reflection.BindingFlags"/> used to search for
/// the method to be invoked.
/// </summary>
private const BindingFlags MethodSearchingFlags =
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.IgnoreCase;
#endregion
#region Constructor (s) / Destructor
/// <summary>
/// Creates a new instance of the <see cref="Spring.Objects.Support.MethodInvoker"/> class.
/// </summary>
public MethodInvoker()
{
Arguments = new object[] {};
NamedArguments = new Hashtable();
PreparedArguments = new object[] {};
}
#endregion
#region Properties
/// <summary>
/// The target <see cref="System.Type"/> on which to call the target method.
/// </summary>
/// <remarks>
/// <p>
/// Only necessary when the target method is <see langword="static"/>;
/// else, a target object needs to be specified.
/// </p>
/// </remarks>
public Type TargetType
{
get { return _targetType; }
set { this._targetType = value; }
}
/// <summary>
/// The target object on which to call the target method.
/// </summary>
/// <remarks>
/// <p>
/// Only necessary when the target method is not <see langword="static"/>;
/// else, a target class is sufficient.
/// </p>
/// </remarks>
public object TargetObject
{
get { return _targetObject; }
set { this._targetObject = value; }
}
/// <summary>
/// The name of the method to be invoked.
/// </summary>
/// <remarks>
/// <p>
/// Refers to either a <see langword="static"/> method
/// or a non-<see langword="static"/> method, depending on
/// whether or not a target object has been set.
/// </p>
/// </remarks>
/// <seealso cref="Spring.Objects.Support.MethodInvoker.TargetObject"/>
public string TargetMethod
{
get { return _targetMethod; }
set { this._targetMethod = value; }
}
/// <summary>
/// Arguments for the method invocation.
/// </summary>
/// <remarks>
/// <p>
/// Ordering <b>is</b> significant... the order of the arguments in this
/// property must match the ordering of the various parameters on the target
/// method. There does however exist a small possibility for confusion when
/// the arguments in this property are supplied in addition to one or more named
/// arguments. In this case, each named argument is slotted into the index position
/// corresponding to the named argument... once once all named arguments have been
/// resolved, the arguments in this property are slotted into any remaining (empty)
/// slots in the method parameter list (see the example in the overview of the
/// <see cref="Spring.Objects.Support.MethodInvoker"/> class if this is not clear).
/// </p>
/// <p>
/// If this property is not set, or the value passed to the setter invocation
/// is <see langword="null"/> or a zero-length array, a method with no (un-named) arguments is assumed.
/// </p>
/// </remarks>
/// <seealso cref="Spring.Objects.Support.MethodInvoker.NamedArguments"/>
public object[] Arguments
{
get { return _arguments; }
set
{
if (value != null)
{
this._arguments = value;
}
else
{
this._arguments = new object[] {};
}
}
}
/// <summary>
/// The resolved arguments for the method invocation.
/// </summary>
/// <remarks>
/// <note type="caution">
/// This property is not set until the target method has been resolved via a call to the
/// <see cref="Spring.Objects.Support.MethodInvoker.Prepare"/> method). It is a combination of the
/// named and plain vanilla arguments properties, and it is this object array that
/// will actually be passed to the invocation of the target method.
/// </note>
/// <p>
/// Setting the value of this property to <see langword="null"/> results in basically clearing out any
/// previously prepared arguments... another call to the <see cref="Spring.Objects.Support.MethodInvoker.Prepare"/>
/// method will then be required to prepare the arguments again (or the prepared arguments
/// can be set explicitly if so desired).
/// </p>
/// </remarks>
/// <seealso cref="Spring.Objects.Support.MethodInvoker.Arguments"/>
/// <seealso cref="Spring.Objects.Support.MethodInvoker.NamedArguments"/>
protected object[] PreparedArguments
{
get { return _preparedArguments; }
set
{
if (value != null)
{
this._preparedArguments = value;
}
else
{
this._preparedArguments = new object[] {};
}
}
}
/// <summary>
/// Named arguments for the method invocation.
/// </summary>
/// <remarks>
/// <p>
/// The keys of this dictionary are the (<see cref="System.String"/>) names of the
/// method arguments, and the (<see cref="System.Object"/>) values are the actual
/// argument values themselves.
/// </p>
/// <p>
/// If this property is not set, or the value passed to the setter invocation
/// is a <see langword="null"/> reference, a method with no named arguments is assumed.
/// </p>
/// </remarks>
/// <seealso cref="Spring.Objects.Support.MethodInvoker.Arguments"/>
public IDictionary NamedArguments
{
get { return _namedArguments; }
set
{
if (value != null)
{
this._namedArguments = value;
}
else
{
this._namedArguments.Clear();
}
}
}
#endregion
#region Methods
/// <summary>
/// Prepare the specified method.
/// </summary>
/// <remarks>
/// <p>
/// The method can be invoked any number of times afterwards.
/// </p>
/// </remarks>
/// <exception cref="System.ArgumentException">
/// If all required properties are not set, or a matching argument could not be found
/// for a named argument (typically down to a typo).
/// </exception>
/// <exception cref="System.MissingMethodException">
/// If the specified method could not be found.
/// </exception>
public virtual void Prepare()
{
if (_targetMethod == null)
{
throw new ArgumentException("The 'TargetMethod' property is required.");
}
if (_targetType == null && _targetObject == null)
{
throw new ArgumentException("One of either the 'TargetType' or 'TargetObject' properties is required.");
}
_methodObject = FindTheMethodToInvoke();
if (TargetObject == null && !_methodObject.IsStatic)
{
throw new ArgumentException(
"The target method cannot be an instance method without a corresponding target instance on which to invoke it.");
}
PrepareArguments();
}
private void PrepareArguments()
{
_preparedArguments = new object[ArgumentCount];
// ok, lets prepare any named arguments first...
if (NamedArguments.Count > 0)
{
// lets slot in all of the named arguments first...
ParameterInfo[] parameters = _methodObject.GetParameters();
// lets figure out the index og each of the method parameters...
IDictionary argumentNamesToIndexes = new Hashtable();
for (int i = 0; i < parameters.Length; ++i)
{
ParameterInfo parameter = parameters[i];
argumentNamesToIndexes[parameter.Name.ToLower(CultureInfo.InvariantCulture)] = i;
}
int THE_ARGUMENT_IS_PREPARED = -12;
foreach (DictionaryEntry namedArgument in NamedArguments)
{
string argumentName = ((string) namedArgument.Key).ToLower(CultureInfo.InvariantCulture);
object argumentValue = namedArgument.Value;
if (!argumentNamesToIndexes.Contains(argumentName))
{
// whoa (Nelly); the named argument does not exist on the method...
throw new ArgumentException(string.Format(
CultureInfo.InvariantCulture,
"The named argument '{0}' could not be found on the '{1}' method of class [{2}].",
argumentName, _methodObject.Name, _methodObject.DeclaringType.FullName));
}
// look up the index of where in the prepared args array we're gonna stick the named argument value
int namedArgumentsIndex = (int) argumentNamesToIndexes[argumentName];
PreparedArguments[namedArgumentsIndex] = argumentValue;
// we've prepped this index position, so mark it as so...
argumentNamesToIndexes[argumentName] = THE_ARGUMENT_IS_PREPARED;
}
// and then fill in any remaining blanks with the plain vanilla arguments...
int plainVanillaIndex = 0;
int[] sortedIndexes = (int[]) new ArrayList(argumentNamesToIndexes.Values).ToArray(typeof (int));
Array.Sort(sortedIndexes);
foreach (int argumentIndex in sortedIndexes)
{
// have we previously prepped a named argument at this index position?
if (argumentIndex == THE_ARGUMENT_IS_PREPARED)
{
continue;
}
// lets stick a plain vanilla argument in at this index position (in the order that they have been supplied)...
PreparedArguments[argumentIndex] = Arguments[plainVanillaIndex++];
}
}
else
{
PreparedArguments = Arguments;
}
}
/// <summary>
/// Searches for and returns the method that is to be invoked.
/// </summary>
/// <remarks>
/// The return value of this method call will subsequently be returned from the
/// <see cref="Spring.Objects.Support.MethodInvoker.GetPreparedMethod()"/>.
/// </remarks>
/// <returns>The method that is to be invoked.</returns>
/// <exception cref="System.MissingMethodException">
/// If no method could be found.
/// </exception>
/// <exception cref="System.ArgumentException">
/// If more than one method was found.
/// </exception>
protected virtual MethodInfo FindTheMethodToInvoke()
{
MethodInfo theMethod = null;
Type targetType = (TargetObject != null) ? TargetObject.GetType() : TargetType;
#if NET_2_0
GenericArgumentsHolder genericInfo = new GenericArgumentsHolder(TargetMethod);
#endif
// if we don't have any named arguments, we can try to get the exact method first...
if (NamedArguments.Count == 0)
{
ComposedCriteria searchCriteria = new ComposedCriteria();
#if NET_2_0
searchCriteria.Add(new MethodNameMatchCriteria(genericInfo.GenericMethodName));
searchCriteria.Add(new MethodParametersCountCriteria(ArgumentCount));
searchCriteria.Add(new MethodGenericArgumentsCountCriteria(
genericInfo.GetGenericArguments().Length));
#else
searchCriteria.Add(new MethodNameMatchCriteria(TargetMethod));
searchCriteria.Add(new MethodParametersCountCriteria(ArgumentCount));
#endif
searchCriteria.Add(new MethodArgumentsCriteria(Arguments));
MemberInfo[] matchingMethods = targetType.FindMembers(
MemberTypes.Method,
MethodSearchingFlags,
new MemberFilter(new CriteriaMemberFilter().FilterMemberByCriteria),
searchCriteria);
if (matchingMethods != null && matchingMethods.Length == 1)
{
theMethod = matchingMethods[0] as MethodInfo;
}
}
if (theMethod == null)
{
// search for a method with a matching signature...
ComposedCriteria searchCriteria = new ComposedCriteria();
#if NET_2_0
searchCriteria.Add(new MethodNameMatchCriteria(genericInfo.GenericMethodName));
searchCriteria.Add(new MethodParametersCountCriteria(ArgumentCount));
searchCriteria.Add(new MethodGenericArgumentsCountCriteria(
genericInfo.GetGenericArguments().Length));
#else
searchCriteria.Add(new MethodNameMatchCriteria(TargetMethod));
searchCriteria.Add(new MethodParametersCountCriteria(ArgumentCount));
#endif
MemberInfo[] matchingMethods = targetType.FindMembers(
MemberTypes.Method,
MethodSearchingFlags,
new MemberFilter(new CriteriaMemberFilter().FilterMemberByCriteria),
searchCriteria);
if (matchingMethods == null
|| matchingMethods.Length == 0)
{
throw new MissingMethodException(targetType.Name, TargetMethod);
}
if (matchingMethods.Length > 1)
{
throw new ArgumentException(string.Format(
CultureInfo.InvariantCulture,
"Unable to determine which exact method to call; found '{0}' matches.",
matchingMethods.Length));
}
theMethod = matchingMethods[0] as MethodInfo;
}
#if NET_2_0
if (genericInfo.ContainsGenericArguments)
{
string[] unresolvedGenericArgs = genericInfo.GetGenericArguments();
Type[] genericArgs = new Type[unresolvedGenericArgs.Length];
for (int j = 0; j < unresolvedGenericArgs.Length; j++)
{
genericArgs[j] = TypeResolutionUtils.ResolveType(unresolvedGenericArgs[j]);
}
theMethod = theMethod.MakeGenericMethod(genericArgs);
}
#endif
return theMethod;
}
/// <summary>
/// Adds the named argument to this instances mapping of argument names to argument values.
/// </summary>
/// <param name="argumentName">
/// The name of an argument on the method that is to be invoked.
/// </param>
/// <param name="argument">
/// The value of the named argument on the method that is to be invoked.
/// </param>
public void AddNamedArgument(string argumentName, object argument)
{
if (NamedArguments.Contains(argumentName))
{
NamedArguments.Remove(argumentName);
}
NamedArguments.Add(argumentName, argument);
}
private int ArgumentCount
{
get { return Arguments.Length + NamedArguments.Count; }
}
/// <summary>
/// Returns the prepared <see cref="System.Reflection.MethodInfo"/> object that
/// will be invoked.
/// </summary>
/// <remarks>
/// <p>
/// A possible use case is to determine the return <see cref="System.Type"/> of the method.
/// </p>
/// </remarks>
/// <returns>
/// The prepared <see cref="System.Reflection.MethodInfo"/> object that
/// will be invoked.
/// </returns>
public MethodInfo GetPreparedMethod()
{
return this._methodObject;
}
/// <summary>
/// Invoke the specified method.
/// </summary>
/// <remarks>
/// <p>
/// The invoker needs to have been prepared beforehand (via a call to the
/// <see cref="Spring.Objects.Support.MethodInvoker.Prepare"/> method).
/// </p>
/// </remarks>
/// <returns>
/// The object returned by the method invocation, or
/// <see cref="Spring.Objects.Support.MethodInvoker.Void"/> if the method returns void.
/// </returns>
/// <exception cref="MethodInvocationException">
/// If at least one of the arguments passed to this <see cref="Spring.Objects.Support.MethodInvoker"/>
/// was incompatible with the signature of the invoked method.
/// </exception>
public virtual object Invoke()
{
object result = null;
try
{
result = this._methodObject.Invoke(TargetObject, PreparedArguments);
}
catch (ArgumentException ex)
{
throw new MethodInvocationException(
string.Format(CultureInfo.InvariantCulture,
"At least one of the arguments passed to this {0} was " +
"incompatible with the signature of the invoked method.", GetType().Name), ex);
}
return (result == null ? Void : result);
}
#endregion
}
}
|