// ==========================================================================
// This software is subject to the provisions of the Zope Public License,
// Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
// WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
// FOR A PARTICULAR PURPOSE.
// ==========================================================================
using System;
using System.Collections;
using System.Reflection;
namespace Python.Runtime {
//========================================================================
// A MethodBinder encapsulates information about a (possibly overloaded)
// managed method, and is responsible for selecting the right method given
// a set of Python arguments. This is also used as a base class for the
// ConstructorBinder, a minor variation used to invoke constructors.
//========================================================================
internal class MethodBinder {
public ArrayList list;
public MethodBase[] methods;
public bool init = false;
internal MethodBinder () {
this.list = new ArrayList();
}
internal MethodBinder(MethodInfo mi) : base () {
this.list = new ArrayList();
this.list.Add(mi);
}
public int Count {
get { return this.list.Count; }
}
internal void AddMethod(MethodBase m) {
this.list.Add(m);
}
//====================================================================
// Return the array of MethodInfo for this method. The result array
// is arranged in order of precendence (done lazily to avoid doing it
// at all for methods that are never called).
//====================================================================
internal MethodBase[] GetMethods() {
if (!init) {
// I'm sure this could be made more efficient.
list.Sort(new MethodSorter());
methods = (MethodBase[])list.ToArray(typeof(MethodBase));
init = true;
}
return methods;
}
//====================================================================
// Precedence algorithm largely lifted from jython - the concerns are
// generally the same so we'll start w/this and tweak as necessary.
//====================================================================
internal static int GetPrecedence(MethodBase mi) {
ParameterInfo[] pi = mi.GetParameters();
int val = mi.IsStatic ? 3000 : 0;
int num = pi.Length;
for (int i = 0; i < num; i++) {
val += ArgPrecedence(pi[i].ParameterType);
}
return val;
}
//====================================================================
// Return a precedence value for a particular Type object.
//====================================================================
internal static int ArgPrecedence(Type t) {
Type objectType = typeof(Object);
if (t == objectType) return 3000;
TypeCode tc = Type.GetTypeCode(t);
if (tc == TypeCode.Object) return 1;
if (tc == TypeCode.UInt64) return 10;
if (tc == TypeCode.UInt32) return 11;
if (tc == TypeCode.UInt16) return 12;
if (tc == TypeCode.Int64) return 13;
if (tc == TypeCode.Int32) return 14;
if (tc == TypeCode.Int16) return 15;
if (tc == TypeCode.Char) return 16;
if (tc == TypeCode.SByte) return 17;
if (tc == TypeCode.Byte) return 18;
if (tc == TypeCode.Single) return 20;
if (tc == TypeCode.Double) return 21;
if (tc == TypeCode.String) return 30;
if (tc == TypeCode.Boolean) return 40;
if (t.IsArray) {
Type e = t.GetElementType();
if (e == objectType)
return 2500;
return 100 + ArgPrecedence(e);
}
return 2000;
}
internal static MethodInfo MatchByTypeSig(MethodInfo[] msig,
IntPtr psig) {
IntPtr args = psig;
bool free = false;
if (!Runtime.PyTuple_Check(psig)) {
args = Runtime.PyTuple_New(1);
Runtime.Incref(psig);
Runtime.PyTuple_SetItem(args, 0, psig);
free = true;
}
int plen = Runtime.PyTuple_Size(args);
MethodInfo match = null;
// XXX: what about out args, etc.?
for (int i = 0; i < msig.Length; i++) {
ParameterInfo[] pi = msig[i].GetParameters();
if (pi.Length != plen) {
continue;
}
bool matched = true;
for (int n = 0; n < pi.Length; n++) {
IntPtr p = Runtime.PyTuple_GetItem(args, n);
if (p == IntPtr.Zero) {
Exceptions.Clear();
break;
}
ClassBase c = ManagedType.GetManagedObject(p) as ClassBase;
Type t = (c != null) ? c.type :
Converter.GetTypeByAlias(p);
if (t == null) {
break;
}
if (t != pi[n].ParameterType) {
matched = false;
break;
}
}
if (matched) {
match = msig[i];
break;
}
}
if (free) {
Runtime.Decref(args);
}
return match;
}
//====================================================================
// Bind the given Python instance and arguments to a particular method
// overload and return a structure that contains the converted Python
// instance, converted arguments and the correct method to call.
//====================================================================
internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw) {
return this.Bind(inst, args, kw, null);
}
internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw,
MethodBase info) {
// loop to find match, return invoker w/ or /wo error
MethodBase[] _methods = null;
int nargs = Runtime.PyTuple_Size(args);
object arg;
if (info != null) {
_methods = (MethodBase[])Array.CreateInstance(
typeof(MethodBase), 1
);
_methods.SetValue(info, 0);
}
else {
_methods = GetMethods();
}
for (int i = 0; i < _methods.Length; i++) {
MethodBase mi = _methods[i];
ParameterInfo[] pi = mi.GetParameters();
int count = pi.Length;
int outs = 0;
if ( nargs == count ) {
Object[] margs = new Object[count];
for (int n = 0; n < count; n++) {
IntPtr op = Runtime.PyTuple_GetItem(args, n);
Type type = pi[n].ParameterType;
if (pi[n].IsOut || type.IsByRef) {
outs++;
}
if (!Converter.ToManaged(op, type, out arg, false)) {
Exceptions.Clear();
margs = null;
break;
}
margs[n] = arg;
}
if (margs == null) {
continue;
}
Object target = null;
if ((!mi.IsStatic) && (inst != IntPtr.Zero)) {
CLRObject co = (CLRObject)ManagedType.GetManagedObject(
inst
);
target = co.inst;
}
return new Binding(mi, target, margs, outs);
}
}
return null;
}
internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw) {
return this.Invoke(inst, args, kw, null);
}
internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw,
MethodBase info) {
Binding binding = this.Bind(inst, args, kw, info);
Object result;
if (binding == null) {
Exceptions.SetError(Exceptions.TypeError,
"no method matches given arguments"
);
return IntPtr.Zero;
}
IntPtr ts = PythonEngine.BeginAllowThreads();
try {
result = binding.info.Invoke(binding.inst,
BindingFlags.Default,
null,
binding.args,
null);
}
catch (Exception e) {
if (e.InnerException != null) {
e = e.InnerException;
}
PythonEngine.EndAllowThreads(ts);
Exceptions.SetError(e);
return IntPtr.Zero;
}
PythonEngine.EndAllowThreads(ts);
// If there are out parameters, we return a tuple containing
// the result followed by the out parameters. If there is only
// one out parameter and the return type of the method is void,
// we return the out parameter as the result to Python (for
// code compatibility with ironpython).
MethodInfo mi = (MethodInfo)binding.info;
if ((binding.outs == 1) && (mi.ReturnType == typeof(void))) {
}
if (binding.outs > 0) {
ParameterInfo[] pi = mi.GetParameters();
int c = pi.Length;
int n = 0;
IntPtr t = Runtime.PyTuple_New(binding.outs + 1);
IntPtr v = Converter.ToPython(result, mi.ReturnType);
Runtime.PyTuple_SetItem(t, n, v);
n++;
for (int i=0; i < c; i++) {
Type pt = pi[i].ParameterType;
if (pi[i].IsOut || pt.IsByRef) {
v = Converter.ToPython(binding.args[i], pt);
Runtime.PyTuple_SetItem(t, n, v);
n++;
}
}
if ((binding.outs == 1) && (mi.ReturnType == typeof(void))) {
v = Runtime.PyTuple_GetItem(t, 1);
Runtime.Incref(v);
Runtime.Decref(t);
return v;
}
return t;
}
return Converter.ToPython(result, mi.ReturnType);
}
}
//========================================================================
// Utility class to sort method info by parameter type precedence.
//========================================================================
internal class MethodSorter : IComparer {
int IComparer.Compare(Object m1, Object m2) {
int p1 = MethodBinder.GetPrecedence((MethodBase)m1);
int p2 = MethodBinder.GetPrecedence((MethodBase)m2);
if (p1 < p2) return -1;
if (p1 > p2) return 1;
return 0;
}
}
//========================================================================
// A Binding is a utility instance that bundles together a MethodInfo
// representing a method to call, a (possibly null) target instance for
// the call, and the arguments for the call (all as managed values).
//========================================================================
internal class Binding {
public MethodBase info;
public Object[] args;
public Object inst;
public int outs;
internal Binding(MethodBase info, Object inst, Object[] args,
int outs) {
this.info = info;
this.inst = inst;
this.args = args;
this.outs = outs;
}
}
}